# 0005. Namespaced content IDs - **Status:** Accepted - **Date:** 2026-06-27 ## Context All registered content (blocks, items, recipes, biomes, entities, and so on) needs a stable identifier that is unambiguous across the engine, data packs, Lua mods, recipe references, and save files. First-party and third-party content must coexist without collision, and identifiers must survive being written to disk and read back. ## Decision Content is identified by a **namespaced string** of the form `"namespace:id"`. - The namespace `core:` is reserved for first-party content (`"core:stone"`, `"core:iron_sword"`). Mods choose their own short namespace (`"mymod:weird_dirt"`). - The strict form is mandatory. A bare id with no `:` is an **error at registration / parse time**, never silently coerced to `core:`. The rule is identical everywhere: engine scripts, data packs, Lua mods, recipe references, save files. - Charset: namespace and id are each `[a-z0-9_-]+` with exactly one `:` between them. Lowercase ASCII only, no uppercase, Unicode, spaces, dots, or slashes. - At registration time each id string is interned into a small integer handle (e.g. `BlockId(u32)`). Hot paths compare handles; the original string is kept for display, save/load, and the Lua API. The reserved namespace is deliberately `core:` rather than the project name, so it remains stable independent of branding. ## Consequences - Identifiers are greppable, filesystem-safe, and unambiguous in logs and save files. - First-party and mod content cannot collide, and the absence of a silent default means mistakes surface immediately rather than corrupting data. - A small runtime cost is paid at registration to intern strings, in exchange for handle comparison on hot paths. - The strict form is a permanent contract: relaxing it later (e.g. defaulting bare ids) would change the meaning of existing save files and is therefore effectively irreversible.