@@ RIGHT - LET'S GET STARTED

SDK 2.0 gives Custom Magic Codes a proper development toolset. We can inspect
racers and objects, change supported values while a game is running, build our
own controller-friendly tools, probe collision, rebuild checkpoints and send
agreed edits through DKR-R Online. The Dev Menu is an ordinary mod using these
public services. There is no secret Dev Menu package ID unlocking extra powers.

We have kept API 1. Existing trophy cups, AI difficulty mods, code packages and
ModernCamera++ do not need to be rewritten just because the authoring kit now
says SDK 2.0. New executable API 2 packages opt into the additional services.
The exact supported modules and limits are part of the reference below.

This book is for v1.0.7 with SDK 2.0. Compilation and package inspection are
not a promise that every possible combination of mods is bug free. Gameplay,
visual, performance, multiplayer and headset testing are carried out by the
players testing the build. Keep backups, work in a development session and
start with one small change at a time.

@@ HOW THIS BOOK IS BUILT

The final appendices reproduce the public headers from the same staged SDK
that is packaged with this build. They are not copied from an older release.
The main chapters explain what those interfaces do, when to use them and where
the engine still needs a dedicated adapter. A capability being unavailable is
not permission to call a protected scheduler, renderer or loader by address.

The public headers are the exact names and layouts to compile against. Where
this book says a feature is restricted, that restriction is intentional. We
are giving modders a lot of freedom without handing a mod the audio device,
graphics queue or another player's local camera. That would just make a mess.

@@ WHAT WE ARE BUILDING

A Custom Magic Code is a portable executable mod. It is not a GameShark text
code, a host DLL, a texture pack or a patched ROM. We compile C, or optional
freestanding C++, to the same MIPS O32 guest format on every platform. The
result is an .nrm package containing code, symbols, metadata and its declared
resources. The app imports it through MODS / HACKS, just like the other mods.

One package can change race rules, react to native functions, expose settings,
create runtime tools, read logical input and keep private state. Packages can
also export functions and events to declared dependencies. SDK 2 adds a typed
development layer around that existing power. It does not take API 1 away.

There are two good starting points. For a normal gameplay mod, use the small
scaffold and request only the modules you need. For development tools, read
the Dev Menu source alongside this guide. It demonstrates handles, edits,
commands, route inspection, documents and the tools UI in one actual package.
Do not copy its large work profile into a tiny banana-count mod for no reason.

@@ INSTALL THE TOOLS

You need Python 3.11 or later, Clang with the MIPS target, LLD and the pinned
RecompModTool from this project's N64Recomp build. Source maps additionally
use llvm-nm and llvm-symbolizer. Use compatible LLVM tools from one toolchain.
The SDK contains the guest headers, runtime helpers, linker script, SDK-only
reference, build script, authoring tools, examples and both revision references.

Windows modders can use WSL. Linux modders can use the same commands directly.
An Android player imports the same .nrm; authors do not build a separate ARM
version of the mod. App binaries are platform-specific, mod guest code is not.
The current app supports the US v1.0 and US v1.1 execution revisions, identified
in code manifests as us.v77 and us.v80. Do not guess offsets from a different ROM.

Keep your project outside the SDK directory, and use a new output directory
for each build. The builder refuses an existing output so a failed compile
does not quietly leave you distributing yesterday's package under today's name.
Neither build.py nor tools.py launches the game or executes your mod.

    python SDK/tools.py new --id my_first_code --name "My First Code" --output my-first-code
    python SDK/build.py my-first-code --output output-001 --mod-tool /path/to/RecompModTool --source-map
    python SDK/tools.py inspect output-001/my_first_code.nrm

Replace SDK and the mod-tool path with your actual locations. On Windows,
invoke these through your chosen LLVM environment or WSL. Paths passed to
WSL must be Linux paths, for example /mnt/c/MyMods rather than C:\MyMods.
Keep the output ELF and source map privately for debugging. Only distribute
the files you intend to share; never put your ROM, save or private log in a mod.

@@ YOUR PROJECT FILES

mod.toml is the RecompModTool manifest and source list. Keep a stable lowercase
ID using letters, numbers, underscores or hyphens. The ID is the identity of
your mod, not its display name. Changing it creates another installed mod.
Increase the version when publishing an edition. Use an honest description,
author attribution, game_id = "dkr" and the minimum app version you actually need.

dkr.json declares executable API, supported revisions, module versions,
deterministic online policy, systems, dependencies and immutable resources.
The metadata schema is still 1 for both APIs. API 2 is selected with api: 2,
not by changing schema to 2. Tools UI resources have their own schema: 2.

    {
      "schema": 1,
      "api": 2,
      "activation": "restart",
      "online": "deterministic",
      "development": false,
      "profile": "standard",
      "revisions": ["us.v77", "us.v80"],
      "requires": {"core": 1, "game-events": 1, "entities2": 2, "transactions2": 2}
    }

Resources map logical IDs to project-relative files. No absolute paths,
parent-directory escapes or symlinks. Declare every resource you use; a mod
cannot fetch an undeclared file from someone's computer. The example Dev Menu
declares tools-ui -> tools.json. The builder can add a source-map resource.
Do not declare your own source-map resource and also pass --source-map.

The [build] sources list in mod.toml selects .c, .cpp or .cc files. Headers and
.inl files are included by those sources; do not compile an include file as a
second source unit. Keep exported/imported interfaces C-compatible.

@@ IMPORTING AND TRUSTING A MOD

Open MODS / HACKS, choose Import Mods and select the .nrm or .dkrcode file.
Find it under Custom Magic Codes, open Details, read its identity and approve
the code only if you trust it. Enable it and start a new game. Existing running
games use their admitted package set, not whatever is now selected in the UI.

A valid archive is not proof that code is safe or sensible. Advanced game
memory writes and native calls can still break a race. Approval is a trust
decision. Give players source, a useful description and clear requirements.
After importing a changed edition, expect approval and preparation to happen
again. Do not use the same filename to hide a different code payload.

For the Dev Menu, import DKR-R-Dev-Menu.nrm and enable it. Enter a game, then
use Custom Magic Code Tools in the overlay or F2. The shortcut can be changed
inside Tools. Back/Select + Y is an optional controller shortcut. The normal
overlay entry remains available if a controller already uses that combination.

@@ API 1 IS STILL HERE

SDK 2.0 is the authoring kit name. Executable API 1 still reports 1 from
dkr_sdk_version. Executable API 2 reports 2. Old import names, struct layouts,
settings, callbacks, dependency ordering and .nrm metadata have not been
renamed. You do not need to convert every existing mod to use the new kit.

The original modules remain version 1: core, memory, events, settings,
resources, random, game, game-hooks, game-events, racers, objects, input,
cameras, collision, hud, audio, racing and trophies. Native-call availability
still depends on the actual dispatcher for the selected revision and backend.
Presentation-domain Camera++ has its separate presentation contract.

Do not change all requires values from 1 to 2. A requirement is a named
capability and version, not the age of the package. A mixed set of API 1 and
API 2 mods is allowed if its dependencies and exclusive systems agree.
Different application engine/protocol versions are still incompatible online.
API compatibility is not a promise that beta 7 can join an older beta lobby.

@@ API 2 MODULE MAP

reflection2 describes typed fields, their ranges, units, read/write flags and
allowed phase. entities2 enumerates current lifetime-checked entity handles.
transactions2 validates and applies batches of typed edits. inspection2 exposes
reviewed symbols and copied guest-memory inspection. None returns host pointers.

operations2 provides typed native operations: teleport, restricted scenery
clone/delete, checkpoint rebuild, original fog/weather, audio and transitions.
catalogues2 exposes frozen course, cup and character identities, admitted asset
banks and active package/resource/hook/callback/settings/audio/operation records.
collision2 provides bounded
static ray/sphere sweeps plus explicitly selected interaction-sphere queries.
events2 exposes the rewindable lifecycle and racer-change queue.

tools-ui2 renders generic mod-defined pages and copied values. simulation2
controls pause, single update and bounded retail timestep. track-tools2 reads
native checkpoints, routes, scene rules, segment bounds, triangle geometry,
AI graph links, spawn points and triggers. documents2 stages user-approved scoped JSON
imports/exports. presentation2 supplies local debug primitives and an explicit
local camera handoff through the tools UI.

racer-tuning2 scopes weight, handling, acceleration and speed-cap overrides
around native racer updates. overrides2 maintains reviewed property overrides,
including native object shading. live-settings2 provides package-local live
numeric/enum overrides, including explicitly opted-in options in other API 2
packages. reload2 enables the restricted offline reload panel.
All these new module versions are 2. Ask dkr2_capability for the actual version.

Availability is not permission to invoke an operation in every scene. A route
needs loaded checkpoint data, a racer field needs a racer, and weather needs
an initialized weather system. Check the returned status every time.

@@ EXECUTION AND THE THREE DOMAINS

Gameplay is deterministic simulation state. Mod globals, allocated heap,
service arenas, event queues, tuning, overrides, handles and RNG are included
in checkpoints. A rewind restores them. Do not put shared gameplay decisions
in a native static variable, physical controller poll or wall-clock timer.

Presentation is local. Browsing pages, selecting inspector rows, debug visibility,
camera motion and UI draft values do not advance shared gameplay or shared RNG.
The UI receives copies, not a live RDRAM pointer. It cannot write a racer while
ImGui is drawing a button. An action must travel back to the simulation owner.

Storage is outside callbacks. A player chooses an import/export file in the
host UI. The host reads or writes bounded documents asynchronously and shows
a review before an import is sent to the mod. There is no arbitrary mod file,
socket, process or system-shell API. The Dev Menu console is not a shell.

Read phases can inspect. Tick phases can apply supported mutations. Draw
phases must not mutate gameplay. A named hook called during rendering does
not become a gameplay callback just because the author calls it one. Wrong
phase returns DKR2_PHASE or stops an unsafe expert operation.

@@ CALLBACKS AND EVENT ORDER

Use DKR_CALLBACK(on_boot) for initial private state. on_frame and on_frame_end
surround normal gameplay updates. Scene unload/load, racer spawn, results and
menu callbacks are emitted only where the actual native adapter owns them.
Do not manufacture a second scene load in your callback.

API 2 adds on_tool at a retired development boundary. It also runs while
development gameplay is paused, so a Resume button can actually be consumed.
Put dkr2_action_next here for a tool mod. Ordinary on_frame is not called for
a parked update. Audio, UI, input transport and the native scheduler remain
alive; a tool pause is not a thread suspension or fake graphics completion.

Named callbacks use before_FUNCTION and after_FUNCTION for eligible native
functions. The dispatcher preserves the port's own hooks. It knows the real
selected revision and owned/offline CPU namespace. Do not resolve a native
function through an unrelated process-global table.

    #include "dkr/development.h"
    static Dkr2Session state = {.size = sizeof(Dkr2Session)};
    DKR_CALLBACK(on_frame) void frame(void) {
        state.size = sizeof(state);
        if (dkr2_session(&state) != DKR2_OK) return;
        /* Read current scene, tick and ownership before using saved handles. */
    }

For event tracing, subscribe with dkr2_events(mask), then drain a bounded number
using dkr2_event_next in a tick. Types cover scene, spawn, despawn, lap,
checkpoint, item and result changes. Queue overflow is explicit event 31,
not silent infinite buffering. Collision hits are queries, not an automatic
collision-event subscription. The queue holds 32 records per owner.

@@ HANDLES - DO NOT KEEP DEAD POINTERS

A Dkr2Handle is a slot and generation, not an address. Object storage can be
freed and reused for a different object at the same address. Generation checks
are there to prevent your old selection from editing that new object.
Scene changes invalidate previous lifetimes. Reacquire targets after a load.

dkr2_entities enumerates at most 64 results in one request. The first parameter
is a slot cursor; OR DKR2_ENTITIES_RACERS for racer-only filtering. Initialize
every result record's size before calling. Slots are one-based handles while
the enumeration cursor is zero-based. Continue from the last returned slot,
not by blindly adding the requested capacity to a sparse list.

    Dkr2Entity racers[8];
    for (unsigned i=0; i<8; ++i) racers[i].size=sizeof(Dkr2Entity);
    int count=dkr2_entities(DKR2_ENTITIES_RACERS, racers, 8);
    if (count>0) {
        Dkr2Read query={racers[0].handle, DKR2_RACER_BANANAS, {.bits=0}, 0};
        int result=dkr2_read(&query,1);
        if(result==DKR2_OK && query.status==DKR2_OK) {
            /* query.value.integer is the signed byte expanded to 32 bits. */
        }
    }

Online inspector targets are copied only at agreed boundaries. A speculative
object birth must not become a command target and later alias after rollback.
The local view can therefore wait for a stable snapshot while the simulation
is predicting. Pausing through an agreed host command is useful for inspection.

@@ REFLECTION AND PROPERTY TYPES

Enumerate dkr2_property(index,&info). Initialize size. The returned metadata
contains ID, type, flags, minimum, maximum, phase, name and unit. Do not treat
an integer's bits as a float or write a float conversion into an ID field.
Dkr2Value is a union; .real, .integer and .bits select its interpretation.

Object properties include position, rotation, scale, opacity, velocity,
behaviour, object ID and segment. Position is intentionally read-only in the
generic editor. Use the teleport operation so segment and path bookkeeping
are updated. Behaviour, object ID and segment are also inspection-only.

Racer fields include forward/lateral speed, bananas, item/ammunition/level,
boost and shield timers/type, AI skill, throttle, brake and buoyancy. Player,
roster index, character, vehicle, lap, checkpoint, position and finished status
are inspection-only. They own more state than a single byte suggests.

Shading fields cover native object ambient/diffuse, primary light colour,
intensity and direction. They need a valid native shading record. Trigger
fields are exposed only for reviewed mode-change, bonus and trigger behaviours.
They do not make every unknown behaviour's private structure writable.

Float values must be finite and inside the published range. Angle16 fields
use signed native angles, not degrees. Direction uses its documented fixed
scale. Game speed units are native units, not a promise of metres per second.
The exact property table and all public constants ship with the SDK reference.

@@ TRANSACTIONS, UNDO AND PERSISTENT OVERRIDES

dkr2_edit accepts up to 32 unique entity/property pairs. It validates target
lifetimes, property types, ranges and duplicates before the first write.
One bad entry rejects the batch. An edit is not an arbitrary C function with
automatic rollback; native calls and manual memory patches are separate work.

    Dkr2Edit change={handle, DKR2_RACER_BANANAS, {.integer=10}};
    int result=dkr2_edit(&change,1);
    if(result==DKR2_STALE) { /* Select a fresh racer. */ }

For undo, capture the previous value first. Store before/after typed edits and
the current lifetime. Undo must still pass normal validation. If the target
has left the scene, an old undo must fail rather than find an address lookalike.
The example keeps 32 edits and clears its history on a scene change.

dkr2_override replaces this owner's complete persistent set, up to 32 fields.
It revalidates ownership before committing and rejects another API 2 owner's
claim to the same field. count=0 clears the set. Normal simulation frames
reapply it; original shading overrides also apply after the native shading
update so the engine does not immediately overwrite them. Unload clears them.

An override cannot turn a read-only field into a writable one. It is not a
generic native-function replacement manager, and it cannot protect you from
an unrelated trusted API 1 mod deliberately writing the same guest bytes.
Declare exclusive systems when those behaviours cannot reasonably compose.

@@ TELEPORTING AND OBJECT OWNERSHIP

DKR2_TELEPORT takes handle slot/generation and float-bit XYZ in its first five
words. It checks the destination, uses reviewed native movement, updates the
segment, clears motion/previous pose and repairs racer checkpoint selection.
Use it to return to a marked test position. Do not set X/Y/Z through raw memory
and assume collision, interpolation and the racing line will catch up.

DKR2_CLONE_SCENERY supports the reviewed scenery behaviour, not racers,
schedulers, doors, cameras or arbitrary resource owners. It copies the bounded
authored entry into owner-controlled storage and calls native spawn_object to
perform initialization. There are at most 32 tool spawns per package and the
native object capacity still applies. It is not memcpy of a live Object.

DKR2_DELETE_SPAWN removes only a matching tool-owned scenery spawn in the same
scene. Deletion is queued through native ownership; a queued delete cannot be
repeated to free an object twice. Another mod's or the track's original object
is not yours to delete. Use a dedicated adapter if a different behaviour needs
special setup, cross-reference repair or cleanup.

@@ RACER, AI AND BOSS TUNING

dkr2_tune accepts a Dkr2Tune with a current racer handle. Mask bits select
weight, handling, acceleration curve and forward speed cap. Factors are
0.1..4. The cap is 0 for disabled or up to 100 native forward-speed units.
Zero mask resets the selected racer; zero handle plus zero mask resets this
owner's tuning. A scene unload drops tuning rather than leaking it into a menu.

The adapter scopes native weight/handling globals around the selected actor's
update and restores them before the next actor. Standard car/hovercraft/plane
acceleration uses a private copy of the reviewed native curve. Boss updates
support the reviewed weight/handling/cap path, not a guessed boss acceleration
array. Requesting an unsupported combination must not silently corrupt it.

dkr2_freeze_cpus skips only the reviewed AI racer update. Object ownership,
audio, graphics, input, transition and scheduler work still run. This is a
debugging tool, not a second global game pause. Resume CPUs before assessing
normal race behaviour. Only one racer-tuning2 provider may own the session.

The Dev Menu can change AI skill, bananas and ammunition; stop motion; maintain
boost, shield and bananas; and apply/reset selected-racer tuning. There is no
typed traction solver, arbitrary boss state-machine editor or extra-human-slot
API. Expert code can hook eligible game logic, but must own the resulting rules.
Vehicle and character identities are read-only here: changing them safely needs
their native scene/model/physics setup, not a live ID-byte switch.

@@ TRACKS, ROUTES AND CHECKPOINTS

The course catalogue is frozen during offline or host preparation. It includes
admitted stock, enabled legacy and Track Lab courses, using logical IDs.
Custom course identity is not its position in Track Select. Menu rows can move;
the logical content identity remains what the match and documents refer to.
Clients verify the host's catalogue rather than building their own order.

dkr2_route reads the native checkpoint/racing line, including position, normal,
width, distance, main count and alternate route. A node includes an entity
handle for selecting it in the inspector. The reviewed native table is bounded
to 127 entries. This is the racing line, not every behaviour's private AI-node
format. Do not treat an alternate-route index as an editable heap pointer.

DKR2_EDIT_CHECKPOINT accepts its entity, float-bit XYZ, native yaw/pitch/roll
and float-bit scale. It validates the node, avoids coincident checkpoint
positions, moves it through native ownership and calls checkpoint_update_all.
That rebuilds the native planes, normals, distances and alternate-route data
together. Editing only a visible marker would leave race logic inconsistent.

Trigger radius/vehicle fields are reviewed for specific behaviours. Other
trigger/door/exit formats, route connectivity changes, new spawn-point layouts,
material remapping and structural meshes require the track authoring/reload
path or a dedicated reviewed code adapter. The inspector and symbol references
help develop that adapter; this release does not claim a universal live mesh
editor or writable material table.

@@ COLLISION PROBES AND DEBUG OVERLAYS

dkr2_cast uses Dkr2Cast and Dkr2Hit. A radius of zero is a ray; positive radius
is a sphere sweep. Segment UINT_MAX searches intersected static segment bounds.
A specific segment restricts the query. Static scene triangles are bounded by
the work budget and per-segment limits. Exhausting fuel returns BUDGET, not an
apparently valid nearest hit from an incomplete scan.

Flag 1 adds ObjectInteraction spheres. Flag 2 skips static triangles; use 3
for dynamic interaction spheres only. These are actual native interaction
spheres, not moving-platform triangle meshes or a new physics engine. A
dynamic hit uses triangle UINT_MAX and texture as the bounded object ID.
A static hit's texture is its render batch texture ID, not a physics-material
classification. Keep those distinctions clear in an editor.

dkr2_debug publishes at most 64 line, box and ASCII-label primitives per
package. They are projected locally with the retained camera and viewport
clipping. No extra world render, GPU ray pass or renderer handle is exposed.
count=0 clears the list. Players can disable local debug display independently
without changing shared mod state or simulation fuel.

The Dev Menu shows nearby route lines/labels, selected-entity bounds and a probe
hit normal. It is a bounded overlay, not every collision triangle every frame.
For a large level, query a small useful area and refresh at a sensible cadence.
More diagnostic output should not make the game impossible to inspect.

@@ COURSE LOADING, MENUS AND CUSTOM CUPS

DKR2_RESTART_SCENE and DKR2_WARP_COURSE use the normal level-property/transition
owners. Warp takes course-catalogue index, entrance and vehicle. The vehicle
must be supported by the admitted course. Warp allows entrance 0 only; other
courses have not constructed their spawn anchors yet. Restarting the current
scene can use an existing entrance with anchors for every current racer.
Do not guess an entrance index that has not been constructed.

DKR2_RETURN_MENU chooses Track Select, Character Select or Start menu. It uses
the normal pause-menu result/teardown path, not a direct jump into a menu
constructor. Pending transitions, menus and post-race viewports can reject
the request. Resume/pause state is reset as the transition takes ownership.

Custom trophy cups remain the API 1 trophies/racing adapter. Use the supplied
Custom Trophy Races example for cup selection, deterministic round order,
logical course selection, extended laps and result/HUD bounds. A runtime cup
picker must preserve that adapter's ownership rather than inventing a carrier
ID and jumping into the middle of trophy_flow.

Increasing only LevelHeader.laps is not enough. Native lap timings, result
cells and Time Trial records have fixed capacities. The reference adapter
owns an 8 by 100 timing table, registers the completed roster, handles the
finish sentinel and bounds result/HUD paths. Native Time Trial must stay off
for extended laps. Keep the exclusive race-laps and trophy-flow systems.

@@ BUILDING YOUR OWN TOOLS UI

Declare tools-ui2: 2, add resources: {"tools-ui":"tools.json"}, and provide a
schema 2 document with pages. There may be 1..16 pages and 256 total fields.
Page IDs and field IDs must be valid, unique within their scope and stable.
Labels are bounded. Page labels allow 80 bytes; field labels allow 160.

    {
      "schema":2, "title":"My race tools",
      "pages":[{"id":"racer","label":"Racer","fields":[
        {"id":"status","type":"text","label":"Last action"},
        {"id":"bananas","type":"integer","label":"Bananas",
         "argument":2,"default":10,"min":0,"max":99},
        {"id":"apply","type":"button","label":"Apply bananas","action":10}
      ]}]
    }

Supported controls are text, button, number, integer, toggle, enum, vector,
colour, search, table and inspector. Diagnostics, authority, console, document,
camera, debug and reload provide reusable host widgets with their named module
requirements. They are not special cases for dkr_development_menu.

readonly makes a field inspection-only. visible names a published boolean,
so a mod can hide an unavailable operation. confirm requests a confirmation
for a button. help adds short guidance. table can select a supplied handle;
inspector can declare edit_action and racer_only. Numeric controls have finite
min/max/default values. Enum choices are an ordered bounded string list.

Use dedicated pages rather than deeply nested scroll areas. The host renders
one main page region with a fixed footer, centred actions, responsive page
navigation and on-screen keyboard access for text/numeric inputs. At narrow
widths the page selector becomes a dropdown. Keep labels short and put long
explanations in the guide, not under every button.

@@ PUBLISHING VALUES AND RECEIVING ACTIONS

dkr2_ui_publish takes a JSON object up to 16 KiB, with at most 128 top-level
values. Keys match field IDs. Tables have at most 128 rows; each row may contain
name/detail and a slot/generation selection. Text fields can display strings or
other JSON values. Numeric fields must receive bounded finite numbers.
Visibility keys must be booleans. Malformed values return INVALID.

Publishing copies presentation data. It is throttled; END can mean no publish
on that tick. Do not branch gameplay on whether the local UI is open. Native
cost measurements and camera/debug visibility are not simulation inputs.
The mod should build the same valid report regardless of which peer reads it.

Action IDs are 1..65531. IDs 65532..65534 are reserved for document upload.
Each Dkr2Action is 64 bytes: owner, command ID, action and 13 words. Only the
first 12 argument words are author parameters. The final word is scene ownership
inserted by the host. Never use it for another slider value.

    DKR_CALLBACK(on_tool) void tools(void) {
        Dkr2Action command;
        int status=dkr2_action_next(&command);
        if(status!=DKR2_OK) return;
        if(command.action==10) {
            Dkr2Handle h={command.arguments[0],command.arguments[1]};
            Dkr2Edit e={h,DKR2_RACER_BANANAS,{.bits=command.arguments[2]}};
            int applied=dkr2_edit(&e,1);
            /* Publish applied as your status; do not assume a queue means success. */
        }
    }

This example requires a development tool session and a selected handle.
Parameters are staged in local UI draft state until Apply is pressed. UI fields
that intentionally share argument indices replace those words as the user
edits them; do not reuse an index for unrelated simultaneous controls.
For a larger tool, allocate arguments deliberately and document every action.

@@ SETTINGS AND FROZEN CONFIGURATION

Scalar settings use Number, Enum or String schemas and the existing launcher
Details editor. Number has finite min/max/default. Enum stores an option index;
String has its bounded text default. Keep IDs stable across versions. The
original dkr_setting_number/string imports remain the read interface.

API 1 settings are frozen for a running session. API 2 can use dkr2_setting_set
and reset for package-local Number/Enum overrides, applied in a tick. They
are checkpointed and cleared on relaunch. The frozen launch settings and
dependency identity are not rewritten. Strings and resource-changing settings
are not silently made live. Relaunch to prepare changed dependencies.

dkr2_catalogue("settings",...) reports this package's schema/current live
value. all-settings reports the canonical package_index and option_index for
every admitted code package, with api, live_allowed, overridden and live values.
API 1 options are always frozen. API 2 does not mean every option is live.

For a mod to accept live edits from a development tool, declare live-settings2
and list reviewed Number/Enum IDs in dkr.json live_options. The tool calls
dkr2_setting_apply(package_index,option_index,&value), or passes null to reset.
It needs a development session and the same ordered online command boundary
as other game edits. The target gets an optional owner-only on_live_setting
callback: option index, value bits, tag. Tag 0 is reset, 1 float, 2 enum. Read
the normal setting imports again when resetting; do not treat tag 0 as zero.
An unchanged override does not generate another callback. Private cached state
and the override both rewind. A genuine callback fault stops safely, not silently.

    /* dkr.json: "live_options": ["pace"] and "live-settings2": 2 */
    DKR_CALLBACK(on_live_setting)
    void changed(unsigned option, unsigned bits, unsigned tag) {
        (void)option; (void)bits; (void)tag;
        pace = dkr_setting_number("pace");
    }

Only opt in settings whose initialization/resource ownership really supports
this. String values, file choices, dependencies and asset-changing options
stay next-launch. A typed dependency export is still useful for more complex
cooperative configuration. Do not pretend the editor can guess it.

examples/live-ai-difficulty demonstrates that contract. It keeps the same
seven AI/boss pace settings and refreshes its private pace cache when the
callback arrives. It owns ai-pace, so do not enable it alongside the original
difficulty mod. The original API 1 example remains unchanged.

Structured configuration uses dkr_data_size/read: immutable canonical JSON
documents supplied at admission. Read in chunks no larger than 4096 bytes.
The trophy cup definitions are a practical example. Runtime development
documents are a separate user-approved channel, not a replacement for the
frozen launch configuration.

@@ DOCUMENTS, SCENE PATCHES AND SHARING

Declare documents2 and a document UI field with a scoped document ID and
import_action. The mod publishes a JSON payload no larger than 12,000 bytes.
The host wraps it with schema, package ID/digest, ROM revision, logical course
and document kind. The canonical wrapper must fit 16 KiB.

The player chooses a .dkrdev path, exports a new file or reads an import for
review. Exports do not overwrite an existing file. The host uses temporary
storage and only publishes the completed new file. File access is not performed
on the simulation thread and is not an arbitrary filesystem service for mods.

An approved import is bounded, checksummed and sent in ordered 40-byte chunks.
Online it uses the same host command stream as other tool actions. A maximum
size document can take several hundred simulation transport frames; this is
not the mod-installation download protocol. No client applies its file locally
while other peers continue with different state.

At the import action, call document_size/read in 4096-byte chunks, validate
the entire payload, resolve every target and only then apply one typed batch.
Call document_accept after processing. Treat missing, ambiguous or stale
targets as rejection. Do not apply the first half and discover the last target
belongs to a different object.

The example's scene-patch document contains deduplicated property edits and
a test-position bookmark. Racers resolve by native roster index; static objects
by ID/behaviour/original position. Missing or ambiguous identities reject the
whole batch. It does not export raw RAM, source track geometry, route topology,
ROM assets or someone's save. checkpoint-patch is a separate single-checkpoint
document: version, checkpoint index, total/main counts, position, angles in
yaw/pitch/roll order and scale. Import checks the counts and uses the native
checkpoint move/rebuild operation once. It does not combine unrelated property
and route commits into a falsely atomic operation. Prepare it from the inspected
checkpoint index; enter the same course and package edition before importing.
Authors can define additional scoped schemas
for reviewed adapters and must validate their own payloads just as carefully.

@@ ONLINE DEVELOPMENT - HOST AUTHORITY

Use Auto/Experimental Rollback for code mods. Other backends do not acquire
shared code support just because a tools panel exists. Enable Development
lobby before hosting a session with development mods. Every racer must consent
before readying. All peers admit the same package editions, dependencies,
frozen configuration, courses and owned-engine contract before startup.

Clients can browse copied inspectors and use local presentation tools. A shared
edit becomes a request to the host. The host approves or declines it, or grants
that peer permission to request subsequent commands without another prompt.
Permission is revocable and scene-epoch scoped. A client request never writes
its local gameplay state immediately. Host-local buttons use the same queue.

The host assigns a fresh ordered command ID. Commands travel in final owner
input records, using the same acknowledgement, repair, conflict handling and
rollback as player input. Predicted commands are empty: a spawn is not repeated
because the next predicted player input looks similar. Commands wait for an
agreed prefix before resource/game-changing work is allowed.

Scene and entity generations reject stale requests. Pending requests and the
shared action audit are visible on Online Development. 'Queued' or 'ordered'
does not prove the operation succeeded; check the mod's returned status too.
Input transport and authenticated request traffic stay active during shared
pause. Step and timestep controls are agreed actions, not per-client cheats.

SDK 2 uses a new network contract. Keep every peer on the matching beta 7
package, not a mix of beta 5/6/7. Never suppress a real VM/native failure and
continue a potentially divergent race. The normal safe-stop remains required.

@@ SAVE PROTECTION

Offline development packages use a separate saves/development namespace,
selected before retail initialization. Their paks are separate too. The route
does not switch halfway through the game or migrate your ordinary adventure
progress into a development profile behind your back.

An online Development lobby starts from the authenticated online seed, but
experimental progress is not committed to the ordinary durable online profile.
The original verified seed is retained for lobby return. This applies even
when a development lobby currently has no development package enabled.

This is a development workflow, not a recovery guarantee for malicious native
code. Keep your own backups. Do not ship a normal gameplay mod with development
enabled merely because it uses API 2: that flag changes save/admission policy.
Conversely, do not remove it from the Dev Menu to make debugging edits look
like an ordinary legitimate progression session.

@@ LOCAL CAMERA AND CAMERA++

presentation2's camera widget asks before taking temporary control of a local
viewport. It does not silently replace ModernCamera++. Releasing control
restores the existing provider. Scene changes and scripted views return to
the normal camera. The retained presentation image owns the render resources;
the tools never write the shared simulation camera to move another peer's view.

The widget supports free/spectator motion, following a selected entity, XYZ,
yaw/pitch, FOV, speed, smoothing and a local position bookmark. Use the existing
camera look/zoom actions when the tools are closed. On a local multiplayer
screen choose the intended viewport. Online, ownership selects the local view.

The private draw closure currently bounds this camera to 1000 world units from
the local racer and the normal loaded-scene envelope. Leaving it restores the
normal camera. This is intentional resource safety, not an unlimited editor
flying anywhere through unloaded geometry. VR/headset support is not claimed
by this SDK/Dev Menu release. ModernCamera++ remains the supplied flat-screen
presentation mod, including the beta 6 shadow interpolation correction.

The camera screen offers Hide race HUD on this screen and Hide tools for
screenshot. HUD hiding is a local decoder scissor within the existing HUD draw
scope; native HUD updates, audio, DMA and task completion still run. It applies
to this machine's race HUD, not another peer's HUD. A new native draw is needed
to change a retained image, so step once if the simulation is paused. Nameplates
and user-enabled performance overlays are separate from the native race HUD.
Scene unload and session return restore visibility. No extra world rendering
or GPU ray tracing is used by these controls.

@@ ENVIRONMENT, LIGHTING AND AUDIO

SET_FOG adjusts the original game's viewport fog, with validated near/far and
RGB values. SET_WEATHER adjusts an already initialized scene weather system.
It does not instantiate rain/snow resources in a dry scene. That needs resource
preparation and reload. These APIs do not reintroduce the removed atmosphere,
dynamic-shadow or volumetric-lighting system.

Object ambient/diffuse/light fields describe native shading, not RTX lights.
Persistent overrides run after the reviewed native shading update. Select an
object that actually owns the corresponding shading record; unsupported
behaviours return UNAVAILABLE rather than interpreting another struct as light.

Audio operations are verified native sound, music, stop and fade commands.
IDs are checked against initialized native banks. The audio catalogue reports
sound/sequence counts; it is not a guessed list of names for custom imported
banks. Sequence IDs must fit the native path's width as well as its bank count.
Online audio still follows the confirmed owned PCM/effect pipeline.

No mod receives an SDL audio device, host mixer pointer or permission to run
a second audio thread. Local volume/mute belongs to the app's existing Sound
settings. A mod wanting continuous custom audio behaviour needs a reviewed
adapter and declared resources, not repeated device initialization calls.

@@ THE DEV MENU - NINE SCREENS

Session shows current scene/tick, authority, save scope and operation result.
Pause/resume/single-update and speed presets use the retired control lane.
Restart and admitted-course warp use normal transitions; the return picker
chooses Track Select, Character Select or Start menu. Reset drops the example's
racer/property overrides. Check the returned status after every request.
An entrance-aware restart requires native spawn anchors for every current racer.
The lap override is limited to 1..3 (0 resets), ordinary races and before any
racer completes lap one. It refuses an admitted race-laps owner, including
Custom Trophy Races. That adapter owns extended timing and cup order; configure
its lineup before launch instead of competing with it mid-race.

Racers / AI selects a racer with the live inspector, changes bananas/skill/
ammunition, stops motion, freezes CPUs, maintains boost/shield/bananas and
applies selected-racer weight/handling/acceleration/cap tuning. A boss does
not magically gain the standard vehicle acceleration curve. Reset before
comparing behaviour against an AI difficulty mod that owns similar rules.

Objects shows typed properties and their units, with supported edits. It also
offers integer/float property actions, undo/redo, teleport, reviewed scenery
cloning and deletion of tool-owned spawns. Selecting a row is local. Applying
an edit is shared offline/host-authorized work. Stale targets need reselection.

Track / Routes lists the frozen course catalogue, native route pages and local
route/target/probe overlays. It moves reviewed checkpoints with native rebuild,
runs static/interaction-sphere probes and prepares property/bookmark/checkpoint documents
for export/import. It does not overwrite the installed .dkrmap or patch a ROM.
The scene inspector pages bounds, triangles, AI links, spawn anchors and triggers.
Highlight a segment box or one triangle's wireframe and normal. This is bounded
debug geometry, not an expensive all-track wireframe renderer.

Camera / Bookmarks marks and returns a gameplay target position, and separately
controls the local presentation camera. Those are different operations: target
teleport affects gameplay, local camera motion does not. Explicit handoff keeps
Camera++ ownership predictable.

Environment / Audio edits original fog, initialized weather and selected-object
shading. It exposes native sound/music/stop/fade controls. None is a replacement
RTX renderer, new atmosphere system or external audio player.

Mods / Values lists active code editions/modules, instruction/native-call fuel,
local physical costs, symbol/hook capabilities, recent deterministic events
and the offline compatible-reload panel. Full exact API references remain in
the SDK; a symbol being visible does not make its native call permitted.
The resource picker browses admitted character/model/header/texture/sprite/cup
identities, native audio IDs, hooks, callbacks and all settings. Apply only an
option marked live_allowed. Other settings require a relaunch.

Commands / History exposes registered actions, typed word arguments, recent
commands and a bounded scene-local macro recorder. Refresh, undo and redo use
the same normal command path as their buttons. Entity-based macros cannot be
replayed safely after a scene change and are disabled there.
Registered actions have completion from current tool fields, parameter hints
and pin-to-favourites. The footer Workspace opens presets and watches.

Online Development shows local/host authority, development save status,
pending client requests, host grant/revoke and recent shared actions. A normal
lobby is not silently promoted to a development lobby by opening this page.

@@ THE COMMAND CONSOLE IS NOT A SHELL

Enter a registered action ID followed by up to 12 words. Decimal integers,
0xHEX and f:float syntax are supported. Negative integers encode signed 32-bit
words. Floating arguments are stored as IEEE float bits. Action IDs are taken
from the immutable tools schema, not arbitrary function addresses.

    105 12 4 10

In the Dev Menu that means action 105 with slot 12, generation 4 and bananas
10. Those handle numbers are only an example. Select a real target first,
or inspect its current lifetime; do not copy a stale example into a live race.
An action with confirm still asks for confirmation when run through the console.

History holds at most 64 commands. A recorded macro holds at most 32 actions
and their parameters in the current tools session. Playback requests each
action through normal authority/lifetime checks. It is not an atomic batch of
32 unrelated native operations; an individual rejected command is still rejected.
The scene-patch document is the durable sharing format for the example's
property edits/bookmark. Session console history/macros are not exported as
an unrestricted script or automatically replayed on the next launch.

@@ COMPATIBLE OFFLINE LIVE RELOAD

An API 2 development package can opt in with live_reload: "instruction-only"
and a nonzero state_schema. Declare reload2 for the host reload widget. Both
the running and incoming editions must opt in with the same schema and ID.
The player selects a fresh .nrm, inspects its identity and explicitly trusts it.
Reload is session-only; installed library files and the selected save route
are unchanged.

The loader relinks the set and requires equal owner/region/function layouts,
imports, hooks, events, profiles, capabilities, settings schema, resources
(apart from source-map), and initial non-executable bytes. Only instruction
bytes may differ. This is deliberately strict. Adding a global, changing a
string constant or altering tools/resources can require a full relaunch.

Cleanup and migration run through on_cleanup and on_reload, for that owner
only. The host preallocates rollback state first. Those callbacks may update
private VM state through core services. Game RAM and other owners' state are
read-only, and native/game/world service calls are forbidden. A callback fault
restores the old instructions, private state, RNG, heap and pending effects.
Do not free/spawn native objects from a cleanup callback.

Tool-owned spawns, active native scopes or a document upload prevent live
reload. Remove/finish them or relaunch. Online code replacement is not allowed:
return everyone to the lobby, import the new edition, readmit dependencies and
ready again. Do not relax the identity check to force a unilateral hot reload.

@@ DIAGNOSTICS AND SOURCE MAPS

Build with --source-map to preserve callback source locations. The builder
uses debug-line information and canonical /mod and /sdk prefixes, avoiding
your local absolute development paths. tools.py source-map can also generate
a map from a suitable ELF. The package inspector prints identity, digest,
API, online/revision/module declarations and member counts.

VM errors include package and callback address; an included source map adds
the callback's source entry. It is not an exact failing instruction/line trace.
Keep the ELF/source version matching the reported package digest. A source
map from another edition can be worse than no source map.

The diagnostics widget reports resident/heap allocation, callbacks, executed
instructions, native calls and local CPU cost. Costs include replayed physical
work. They are local measurements, not checkpointed simulation clocks and not
values to use for deterministic gameplay decisions.

Inspect the first meaningful error, not just the final disconnect. INVALID
means check types/ranges/IDs. STALE means reacquire the entity. PHASE means use
the proper tick or confirmed transition. AUTHORITY means development/host/owner
permission. UNAVAILABLE means no reviewed adapter here. BUDGET means reduce
work. CONFLICT means ownership/duplicate work. RELOAD means prepare resources
or restart; it is not a request to bypass a guard.

@@ WORK AND MEMORY BUDGETS

compact requests a 32 KiB heap, 100,000 instruction callback ceiling and 32
native calls. standard requests 64 KiB, 200,000 and 128. large requests 256 KiB,
500,000 and 256. API 1 keeps its existing allocation/limits. API 2 requests are
admitted before launch; the combined request ceiling is 2,000,000 instructions
and 1024 native calls, within the existing 8 MiB resident mod budget.

The game world also has a shared tick budget. Nested calls do not manufacture
fresh fuel. A package ceiling is not permission to consume that much for every
entity or import. Native work and collision scans are charged. Callback depth,
private VM/native stacks and the native call closure stay bounded.

Other important bounds: 512 handles per owner, 32 edits per batch, 32 persistent
property overrides, 32 tool spawns, 32 queued events, 64 debug primitives,
128 settings/options, 128 top-level published UI values, 16 pages, 256 fields,
16 KiB published UI/doc wrapper, 12,000-byte document payload and 4096-byte
resource/data/read chunks. The native race roster remains at most 8 and local
human input ownership remains at most 4. This is not an unlimited roster mod.

A package is at most 32 MiB compressed input and 64 MiB expanded, with at most
512 archive members. There are further symbol/code/relocation and dependency
limits. Treat a limit error as useful feedback. Avoid enormous per-frame JSON,
one import per triangle and full catalogue rebuilding every tick. Cache immutable
metadata in your private state and page large inspectors.

@@ EXPERT GAME MEMORY AND HOOKS

dkr_game_symbol and symbol_size resolve reviewed named data. Unknown names
return no pointer. The SDK includes revision-specific symbol and decomp layout
references. Check the selected revision and field extent before using an expert
pointer. Never ship a raw address from one ROM as if it were revision-independent.

Named before/after callbacks and DKR_HOOK/DKR_HOOK_RETURN support eligible
native functions. DKR_REPLACE is exclusive replacement, not a cooperative
modifier. Declare incompatible systems/conflicts rather than depending on
load order. A replacement must preserve the actual calling convention and
native ownership invariants. A hook's argument registers are caller-saved;
an exit hook must not assume its entry arguments survived the original call.

DkrGameCall carries guest integer/FPU context, HI/LO and FCSR. Outgoing O32
arguments 5 onward are stack words; align the guest stack. dkr_call4 is the
convenience helper for four integer-word arguments, not a universal float/
struct-return ABI converter. Use the exact decomp signature for anything else.
The public headers document context access, skip-original and bounded repeat.

System/scheduler/thread/renderer/device and unsafe loader ownership stays
protected. Do not call initialization twice, replace an allocator while live
objects exist, free another system's object, or patch render buffers from a
gameplay callback. Expert access is powerful and trusted; it does not turn
arbitrary native code into a safe transaction.

@@ FREESTANDING C++

Use tools.py new --language cpp for the optional C++ scaffold. The same public
C ABI remains underneath. development.hpp adds small handle/query/UI helpers;
it is not a host plugin framework. The builder uses C++17 without exceptions,
RTTI or thread-safe static initialization. Keep exported callbacks extern "C".

No host STL, host threads, filesystem, networking or dynamic library loading.
Avoid a dependency that quietly expects libc++, exceptions, global constructors
or an OS allocator. Use dkr_alloc/free for bounded guest allocations and the
included runtime helpers. Do not assume new/delete or a full C++ standard
library is present just because the source extension is .cpp.

Keep interchange records fixed-width O32 fields. No platform-sized long,
size_t or host pointer in a published record. Header static_asserts check
handle, edit, entity, action, collision, event, route, debug and tuning sizes
when compiled as C++. Preserve size fields and initialize them for queries.

@@ DEPENDENCIES, SYSTEMS AND COOPERATIVE MODS

Declare package dependencies with the minimum compatible version. Resolution
creates a canonical order before activation. Missing dependencies, conflicting
packages, duplicate replacements and exclusive systems reject at preparation,
not halfway through a race. No process-global monkey patch should decide who
wins just because one machine loaded a mod first.

Use exported functions/events for cooperation. Let one owner validate a live
configuration change, then expose an event/result to dependents. Keep the event
payload a documented guest ABI. Do not pass a host pointer or expect a borrowed
temporary buffer to outlive its callback.

Only one simulation2 provider and one racer-tuning2 provider may own a session.
The Dev Menu also declares development-tools. Existing trophy race-laps and
trophy-flow ownership is retained. Typed persistent field ownership is checked
between API 2 override providers. API 1 expert mods still need honest conflict
metadata when they touch the same native systems.

@@ WORKSPACES, FAVOURITES AND MULTI-TARGET WATCHES

Right - you do not need to set the same inspection layout up every time.
Open Workspace in the tools footer. It has one dedicated scroll area, the
current watch list, a path textbox with the on-screen keyboard and a fixed
Back action. Save different named .dkrtools files for different jobs.

A workspace stores the current page, numeric drafts, declared search fields,
up to 128 pinned actions and up to 32 watched fields. It is edition-scoped:
package ID/digest and schema must match. Export does not overwrite an existing
file. Reading a file shows a review before applying it. Import only changes
local UI drafts and watch definitions. It never starts a race, submits an edit,
runs a macro or changes an installed mod setting by itself.

Pin a typed field from the inspector. A watch keeps its racer index or static
object ID, behaviour and original XYZ, not a process pointer. Within the same
scene it keeps the actual handle generation. If that object dies, it stays
stale instead of attaching itself to the next allocation at the same address.
On a new scene or an imported workspace, a unique symbolic match can rebind.
Missing or ambiguous matches remain unresolved. You can explicitly rebind a
watch to the selected entity or remove it. Do not read a stale value as current.

Recorded command macros retain their original local session and scene token.
They are only restored from a workspace within that same session/scene. Old
macros containing entity handles are dropped on import into a later game run.
Favourites, numeric drafts and symbolic watches remain useful across runs.
This is intentional - a persistent macro is not permission to hit a different
object that happens to reuse slot 15 next time.

The exporter excludes file/reload paths, imported document contents, native
addresses, lobby secrets and private save data. Only schema-declared search
text is eligible. Workspaces are bounded to 32 KiB and every field is validated
before replacing local drafts. They are not executable scripts or a shell.

To save a camera/test position across runs, use the reviewed scene-patch
bookmark document or enter the coordinates as a local camera preset. A bare
handle is never a portable bookmark. Keep a copy of your original source track.

@@ RESOURCE AND LOADED-SCENE CATALOGUE REFERENCE

Use dkr2_catalogue(name,index,buffer,capacity). It returns the required byte
count including the terminating NUL, END past the list or a negative status.
Pass a bounded buffer and check the result before parsing. JSON records are
at most 4095 bytes, have explicit scope and use logical IDs. Enumerate a small
page, not every asset on every frame. Disabled/unadmitted packs are not secretly
loaded just because an editor wants their names.

courses includes stock, admitted legacy and modern Track Lab tracks with
carrier, vehicles and stable content identity. cups includes admitted cup
metadata. Start cups in Track Select so the normal trophy owner sets lineup,
rounds, order and timing. Do not manually write a trophy flow global.

characters includes the ten native characters and admitted custom characters.
character-assets describes admitted custom portrait/header/stage/model IDs.
These describe resources, not permission to swap a live racer's ID. Re-enter
Character Select or use a reviewed scene/model initialization adapter instead.

models, textures-3d, textures-2d, sprites, object-templates and level-models
are indexed records in the admitted boot namespace (or native base bank when
no legacy namespace exists). Each record includes its bank fingerprint, section,
count, index and scope. A global object-header index is not automatically a
level-entry spawn-template ID. Clone scenery uses a real current object entry,
not an arbitrary number typed from this catalogue. No ROM/texture bytes or host
source paths are exposed by the metadata browser.

sounds and sequences enumerate initialized native IDs. audio provides their
counts. resources lists the calling package's declared immutable resources;
packages, hooks and callbacks list actual admitted owners. all-settings includes
the exact live-edit opt-in contract. Native sound names are not guessed.

track-tools2 adds these current-scene lists:

    scene          Current map/race/vehicle/lap/fog and course-height metadata
    segments       Bounds plus vertex/triangle/batch counts for each segment
    triangles/N    A triangle in segment N: three vertices, normal, texture ID
    ai-nodes       Native graph node ID, elevation and cached adjacent links
    spawn-points   Racer anchor, entrance, vehicle, yaw, position and segment
    triggers       Reviewed trigger position, radius, vehicle and behaviour

These are bounded read-only snapshots of the loaded guest scene. Triangle
texture is a render-batch identity, not a claim to be a universal physics
material. The triangle inspector/highlight complements collision ray/sweep
results; it does not rewrite a moving platform's collision mesh. Water height,
wave parameters and material changes need the proper prepared resource path,
not a blind write to a field with a similar-looking name.

The Dev Menu highlights one segment box or one triangle wireframe/normal at
a time, together with target/probe markers. Switch back to Racing route to
restore checkpoint overlays. Lists and debug work stay within portable budgets.
There is no full-track extra render pass and no GPU ray-tracing dependency.

@@ WHAT IS NOT A UNIVERSAL TYPED EDITOR

The SDK gives broad expert hooks and game memory access plus reviewed typed
tools. It does not promise a safe setter for every decomp field. Character/
vehicle swapping, route-link topology, spawn-point table creation, arbitrary
boss state machines, moving-mesh collision and live structural geometry need
their own native lifecycle adapter or a prepared scene reload.

The catalogues cover admitted stock/legacy/Track Lab courses, custom cups,
characters, model/header/texture/sprite banks and initialized native audio,
alongside active code packages, hooks, callbacks and settings. They are not an
offline disk-library search for disabled packs, nor a symbolic name for every
sound in a modified ROM. Resource counts/IDs come from the admitted bank, not
invented placeholder names. The decomp references remain available for experts.

The example supplies a live typed inspector, multi-target watches, favourites,
reviewed workspaces, bounded event trace, history/macros and property/bookmark/
checkpoint documents. It does not claim an arbitrary persistent command script,
unlimited database, complete AI-nav editor or offline level-authoring suite. Use dedicated
tools for structural assets and extend reviewed public adapters for genuinely
new runtime behaviour. Unsupported work is explicit, never a hidden no-op.

@@ MIGRATING AN EXISTING MOD

First build it unchanged with the compatibility headers and api: 1. Keep its
ID, scalar settings, data documents, exclusive systems and revision declarations.
Do not add development mode unless it is actually a development tool. Compare
your published ABI/manifest and retain the old edition for recovery.

Only opt into api: 2 when you need a new module. Add the exact requires entry,
choose a sensible profile, initialize size-tagged structs and handle status
returns. Replace stale raw entity pointers with reacquired handles where you
can. Move UI actions out of draw hooks and into deterministic tool commands.

Keep API 1 frozen settings semantics unless the author deliberately adopts
live numeric/enum overrides. New data formats need an explicit version and
validation/migration path. Never quietly overwrite imported cup definitions,
installed mods or a normal adventure save just to make an upgrade look seamless.

For a camera mod, stay in the local presentation domain. For a shared rules
mod, stay in the simulation domain and use admitted logical input/RNG. A single
package can cooperate with other packages, but it must not use one peer's
local camera or native profiling time as a shared gameplay input.

@@ A SENSIBLE DEVELOPMENT WORKFLOW

Start with one mod and one ordinary course. Get the small change right before
adding a dozen hooks. Log a short initialization message and meaningful failures,
not an unbounded report per racer per frame. Keep the current source, ELF,
manifest, package digest and guide together in version control.

Use a development save/session for tools. Confirm that leaving a scene clears
handles, undo selections, native scopes and spawned ownership. Read results
after operations. A UI click only queued a request; it did not bypass authority,
phase, memory or work validation.

Check both supported revisions if you advertise both. For online work, players
need matching app/package editions and admitted settings. Exercise host and
client requests, declined requests, scene changes, pause/resume and a late
input correction. Do not call a compile a multiplayer acceptance result.

Gameplay, visuals, performance, controller behaviour and multiplayer acceptance
for this playtest are carried out by the people running it. The distribution
records compiler/package checks separately. No game or mod was executed by
the build agent as part of those checks.

@@ TROUBLESHOOTING WITHOUT GUESSWORK

Missing import/module: check api, exact requires name/version, selected ROM and
backend. Unknown settings/resource: match stable logical IDs and include the
declared file. Package rejected: inspect archive paths, sizes, duplicate names,
manifest game_id and module requirements before weakening any guard.

Edit keeps reverting: the native behaviour may own that value every update.
Use a reviewed persistent override or a scoped behaviour hook, not a faster
UI timer. Teleport snaps back: you bypassed native path/segment bookkeeping or
another racing system owns the target. Use the teleport adapter and inspect
the returned status.

No route/weather/shading: the current scene/object may not own that system.
Enter the proper scene or use an adapter that prepares its resources. A normal
dry scene cannot safely acquire snow by writing one enable flag. A wrong object
struct cannot safely become a light just because the offsets look plausible.

Online action has no immediate effect: it may be waiting for host approval,
transport repair or the agreed prefix. Inspect authority/history and the mod's
result. If a scene changed, reselect the target and send a fresh request.
Never retry a spawn by applying it locally on only one peer.

Reload rejected: compare initial data, function/region layout, imports, hooks,
resources, options, profile and state_schema. Instruction-only is intentionally
narrow. Use a relaunch when the new edition changes those things. Do not change
the digest or receipt manually to make an incompatible image look compatible.

@@ PUBLISHING YOUR MOD

Ship the .nrm, source/license if appropriate, exact minimum application/API/
module versions, supported revisions, conflict/dependency notes and a short
explanation of what changes. Say whether it is a normal gameplay mod or an
isolated development tool. Give people a clear way to disable it and start again.

Inspect the final archive, not just the build directory. Exclude ROMs, saves,
private configuration, personal absolute paths and huge debug artifacts you
did not intend to publish. Keep source maps free of local path leaks. Testers
should report app version, mod digest/edition, ROM revision, backend and the
first useful runtime error.

Do not advertise 'changes absolutely anything safely' or 'bug free' just because
the VM admitted a package. The point of SDK 2.0 is that we can do a lot more,
with proper tools and explicit ownership. Keep the power, keep the limits
honest, and give the next modder enough information to build on your work.

@@ WORKED EXAMPLE - A SETTING THAT CHANGES GAMEPLAY

The kit's guide-starting-bananas is a deliberately small complete API 1 example.
It is useful even if your next project uses API 2, because it shows the whole
route from a launcher setting to an admitted deterministic native-roster edit.
Its dkr.json requires core, settings, game and racers at version 1, declares
both supported revisions and owns the starting-bananas system.

Put scalar settings in mod.toml under manifest.config_options. The builder
turns these into the package settings schema. Do not create a random settings
resource and expect the launcher to infer that it is a settings menu.

    [[manifest.config_options]]
    id = "bananas"
    name = "Starting bananas"
    type = "Number"
    default = 5.0
    min = 0.0
    max = 10.0

    [[manifest.config_options]]
    id = "affect"
    name = "Apply to"
    type = "Enum"
    options = ["Everyone", "Humans only", "AI only"]
    default = "Everyone"

    [[manifest.config_options]]
    id = "label"
    name = "Session label"
    type = "String"
    default = "Starting Bananas ready"

on_boot reads bananas through dkr_setting_number and rounds inside its declared
range. affect is the enum index, not its visible string. The label demonstrates
the length-query pattern: ask dkr_setting_string with a null destination, check
the required bytes including NUL, then read into a sufficiently large buffer.
Do not pass a 16-byte array for a possible 96-byte string.

    DKR_CALLBACK(after_track_setup_racers) void apply_start(void) {
        unsigned count=dkr_racer_count();
        for(unsigned i=0;i<count;++i) {
            DkrRacerState racer;
            if(!dkr_racer_get(i,&racer) || racer.finished) continue;
            if(affected==1 && racer.player<0) continue;
            if(affected==2 && racer.player>=0) continue;
            dkr_racer_set(i,DKR_RACER_BANANAS,starting_bananas);
        }
    }

The complete source defines affected and starting_bananas in on_boot. This is
a roster-setup teaching example: demos and menu rosters can also use that native
setup. A published race-only rule needs reviewed game-mode/race-type guards.
Do not copy a callback name and assume it runs only in the exact scene you had
in mind. Keep the original .nrm for comparison while developing a new edition.

@@ REVIEWED OPERATION WORD REFERENCE

dkr2_operation takes an operation ID plus two 16-word arrays. Zero unused
arguments. Floats are IEEE float bits; signed values occupy a 32-bit word.
Availability checks the selected CPU dispatch closure, then the call checks
phase, authority, scene data, ranges and budgets. A nonnegative availability
preview does not skip those runtime requirements.

    1 TELEPORT:       [0]slot [1]generation [2..4]float XYZ
    2 CLONE_SCENERY: [0]template slot [1]generation [2..4]float XYZ
    3 DELETE_SPAWN:  [0]tool-owned slot [1]generation
    4 CHECKPOINT:    [0]slot [1]generation [2..4]float XYZ
                     [5]yaw [6]pitch [7]roll [8]float scale
    10 FOG:          [0]viewport [1]near [2]far [3]R [4]G [5]B
    11 WEATHER:      [0]velocity X [1]Y [2]Z [3]intensity
                     [4]opacity [5]transition ticks
    20 SOUND:        [0]sound ID
    21 MUSIC:        [0]sequence ID
    22 STOP MUSIC:   no parameters
    23 FADE MUSIC:   [0]signed native fade value
    30 RESTART:      [1]entrance (0 or a fully anchored existing entrance)
    31 WARP:         [0]frozen course index [1]entrance 0 [2]vehicle
    32 RETURN MENU:  [0]0 Track Select / 1 Character Select / 2 Start menu
    40 RACE LAPS:    [0]1..3 laps; 0 resets this owner's override

Clone result[0] is an owner spawn-storage slot, not an immediately valid entity
handle. Re-enumerate after native initialization to obtain the lifetime handle.
Do not feed that storage index into DELETE_SPAWN as if generations were optional.
Restart uses the active playable course and default vehicle; it does not make
a new resource-changing vehicle/character choice live.
Race laps only changes an ordinary first-lap race with no race-laps mod owner.
It never increases the native three-slot result arrays or edits cup sequencing.

Fog viewport is 0..3, near 0..1018, far up to 1023 and at least five greater
than near. RGB is 0..255. Weather velocity is bounded to +/-1048576 native
units, intensity/opacity 0..65536 and transition 1..3600 ticks. Weather must
already be initialized. These are native units, not a graphics preset system.

Simulation control is a separate dkr2_control call: 0 resets/resumes, 1 pauses,
2 grants one update while paused, 3 sets retail logic units 1..4. This scales
the retail timestep; it does not simulate four extra fully subdivided physics
ticks or change audio pitch. Use bounded debugging speeds, not a timing rewrite.

@@ INPUT, HUD AND IMMUTABLE RESOURCES

API 1 dkr_input_get reads recorded logical held/pressed/released buttons and
stick axes for a native player port. These are the inputs the game owns, not
an independent SDL device read. Online decisions must use admitted logical
inputs or ordered commands, never whichever physical controller happens to be
connected to this computer.

dkr_hud_text works only inside an eligible drawing callback with an already
loaded font and the current menu/HUD list owner. Pass target, font, screen
coordinates, RRGGBBAA, alignment and printable ASCII. Text is at most 160 bytes
per call and 512 glyphs per package/frame. Do not recurse from inside draw_text
or invent a display-list pointer. A full custom texture/widget renderer needs
a dedicated rendering adapter, not a string converted to a GPU address.

dkr_resource_size/read accesses declared immutable resources by logical ID.
Query size, allocate a bounded buffer or stream in <=4096-byte chunks, and
validate your format before using it. dkr_data_size/read is similar but reads
the canonical frozen structured configuration, not the original resource file.
Both are stable admitted data, not live disk reads during a replay.

Use dkr_random_u32 for gameplay randomness. Its per-owner stream is checkpointed.
Taking an extra random number because a UI page is open changes the simulation;
do not do that. Use local presentation state for cosmetic UI randomness, or
derive a deterministic visual choice without changing the shared stream.

dkr_alloc uses the package's bounded guest heap and 16-byte alignment. Failed
allocation returns null. Check it. Free only a live allocation from that heap,
once. A native Object pointer is not an SDK heap allocation. A callback-local
array or temporary document buffer cannot be retained by another callback
unless its lifetime and ownership actually extend that far.

@@ EXAMPLE SOURCE TOUR

examples/dev-menu/mod.toml identifies the ordinary portable example and selects
main.c. dkr.json opts into API 2, development saves, the large portable profile,
its explicit version-2 modules and instruction-only reload state schema.
tools.json is the generated immutable nine-page tools resource.

main.c owns private command state, the current handle, property history,
bookmarks, event trace, route/probe snapshots and typed override selections.
The action switch is the public command protocol. on_tool consumes actions
and publishes bounded snapshots; on_frame applies the selected ordinary
gameplay overrides. Neither depends on an ImGui pointer or a secret host export.

documents.inl defines the example's small scene-patch payload and target
resolution. It validates all identities and values before dkr2_edit commits.
The source distinguishes static object identity from a racer roster index and
rejects ambiguous matches. Reuse that pattern rather than exporting raw RAM.
track.inl handles bounded geometry highlights and the separate checkpoint-patch
schema. The whole example compiles through the same public imports as your mod.

The native engine's generic tools renderer, input broker and service adapters
are implementation details, not extra functions a mod can call by package ID.
The shipped headers and named module policy are the supported interface. If a
new tool genuinely needs a new public service, add a versioned reviewed adapter
instead of reaching around the SDK from the example.

@@ EXACT PUBLIC HEADER REFERENCE

The following appendices are extracted from the same staged kit as the packaged
SDK 2.0. They include the original API, convenience game snapshots/setters,
new development services, C++ helpers, bounded JSON writer/reader and local
presentation ABI. Read the comments as part of the contract, not optional hints.

Use the revision references and example source shipped beside them for native
layouts and larger adapters. The Dev Menu is examples/dev-menu. The original
trophy and difficulty examples remain in the kit. The exact package hashes,
engine identity and compile/package evidence are recorded with the delivery.
