Agent skill

Luncosim Architecture

by LunCoSim in LunCoSim/lunco-sim

Review or build a LunCoSim feature that crosses USD, Modelica, Avian, Rust, or Rhai.

Apache-2.0Auto-check passed

Install Luncosim Architecture

skills CLI
$ npx skills add LunCoSim/lunco-sim --skill luncosim-architecture -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install LunCoSim/lunco-sim luncosim-architecture --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/LunCoSim/lunco-sim.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/luncosim-architecture .claude/skills/luncosim-architecture && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
luncosim-architecture
GitHub stars
107
Token cost
~19k tokens
SKILL.md length
10,184 words
Files
2 (incl. references)
Skills in repo
40
Repo updated
First seen
Licence
Apache-2.0

At a glance

Review or build a LunCoSim feature that crosses USD, Modelica, Avian, Rust, or Rhai.

  • Works in 4 steps: Inspect the vendored OpenUSD schemas in… → Use the standard field when it exists:… → Add a LunCo schema only for semantics… → …
  • SKILL.md covers Plugin and crate layering, Source-backed program attachment, Start with the standard-schema… and Classify a gap before changing…, plus 5 more sections
  • Calls kind, cargo and python3

What it does

Luncosim Architecture is an agent skill from LunCoSim/lunco-sim. Review or build a LunCoSim feature that crosses USD, Modelica, Avian, Rust, or Rhai. Use this when adding a reusable component, sensor, actuator, controller, generated Modelica network, USD schema, or runtime projection, and when removing legacy paths, shims, compatibility fallbacks, or custom fields that duplicate an OpenUSD standard.

Its SKILL.md is about 19k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including reference files (for example `references/usd-standard-map.md`).

It works with Rust. The repository describes itself as: Collaborative Multiphysics Cosimulator For Space Missions 🌎🚀🌚. The licence is Apache-2.0.

Example prompts

  • “/luncosim-architecture”

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. Inspect the vendored OpenUSD schemas in crates/lunco-usd-document/schema/core/ and
  2. Use the standard field when it exists: UsdGeom for transforms, geometry,
  3. Add a LunCo schema only for semantics that have no standard owner, such as
  4. If a custom field overlaps a standard field, migrate every reader and asset

What it can do on your machine

Read from SKILL.md and the folder at commit 43f1301. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • kind
    • cargo
    • python3

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Luncosim Architecture loads about 19k tokens when it runs, and up to ~20k if it reads all its reference files. Until then it costs about 90 tokens; SKILL.md has 10,184 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~90
When it runs · the whole SKILL.md, loaded when a task matches
~19k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~20k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from LunCoSim/lunco-sim at commit 43f1301, republished under its Apache-2.0 licence (© LunCoSim). 10,184 words, ~19,257 tokens.

Download SKILL.mdSave it as .claude/skills/luncosim-architecture/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.
name
luncosim-architecture
description
Review or build a LunCoSim feature that crosses USD, Modelica, Avian, Rust, or Rhai. Use this when adding a reusable component, sensor, actuator, controller, generated Modelica network, USD schema, or runtime projection, and when removing legacy paths, shims, compatibility fallbacks, or custom fields that duplicate an OpenUSD standard.

LunCoSim architecture

Use this skill before changing a reusable engine feature. Keep the authored system declarative and composable, and make each concern live in its native representation:

Before calling an architectural capability missing or impossible, use capability-discovery. Search the relevant skills and architecture docs, identify the standard or project owner, inspect registrations and callers, check maintained dependencies, and verify the production/API surface when relevant. Classify the result as found, implemented-but-unwired, present on another branch/version, not found in the searched scope, or externally blocked. Do not create a second owner, fallback, or compatibility shim because the first search was incomplete.

ConcernAuthoritative ownerRuntime role
Scene structure, identity, topology, frames, connections, component parametersUSDRust projects the composed stage; it does not invent missing topology
Continuous equations, state, control laws, filters, physical networksModelicaRumoca compiles and steps the model
Rigid-body collision, contacts, forces, torques, jointsAvian through USD-authored physicsRust exposes the engine's generic mechanics and executes them
Mission phases, events, policy, objectivesRhai or behaviour treesTask/event orchestration in production; on_tick is test-only for sampled verdicts
Engine mechanisms, projection, scheduling, hot pathsRustGeneric implementation; no vehicle- or sensor-name special cases

For cross-domain execution, read 62-deterministic-runtime-and-async-boundaries.md. Keep parsing, source resolution, and immutable preparation off the UI/fixed schedule when inputs can be captured by revision. Admit results only at an owner boundary in stable identity order. Keep live-world hooks and physics inside their deterministic schedule. The required Rhai USD composition policy decides incomplete initial stage and newly mounted reference closures before their respective admission and projection boundaries. A Rhai scenario that depends on Modelica ports or events declares the participating entity ids in simulation_dependencies(me, ctx), where ctx is the validated scenario parameter map. The hook returns a map with modelica_entities: [ids], entity_reads: [ids], entity_writes: [ids], query_reads: ["PublicQueryName"], and required_inputs: [#{ owner, identity }]. All five arrays are required even when empty. The owner resolves all declared ids once per source/parameter revision. modelica_entities ids must identify live Modelica participants and grant both directions of access. Directional read/write ids may identify any live entity; a Modelica entity in either set also joins the shared causal barrier. Unresolved ids fail that source revision with a visible diagnostic. Every simulation-clock Modelica port access must be covered by the calling scenario's own plan, including get, port, and query("ReadPorts", #{ api_id, port_names }); aggregate barrier membership from USD wiring or another scenario does not authorize the read or write. The owner enforces this at the port, query, targeted-command, and event-delivery boundaries. Generic reflected reads and direct mutations also require the matching entity_reads or entity_writes declaration. After a typed simulation command materializes an entity whose id was not available during plan preparation, track_entity_read(id) or track_entity_write(id) adds the directional access in serialized simulation order before the entity is used. Tracked Modelica entities join the same barrier when they enter the current Modelica projection. Entity-targeted API query providers report their live target ids through simulation_entity_reads; the scripting bridge checks them against the scenario's read plan before execution. Broad spatial/world queries use an explicit coarse declaration by public provider name in query_reads. Mounted-scene USD queries without doc_id use the active committed Twin generation. Unknown query providers default to the broad declared scope, and broad queries cannot run while simulation_dependencies is resolving. Required input keys refer to producer namespaces registered in the generic SimulationDependencyStates resource. The scenario holds its existing ScriptPreparation key until every required input is Ready; it retries only after an owner-published state revision, and the visible wait reason names the input. A missing producer is a terminal diagnostic; a producer's Failed state reports its errors. Omitting the hook declares no Rhai dependencies. The plan runs before mutable top-level initialization, so derive it from me, scenario parameters, and read-only world queries. Top-level initialization runs in its own Initialization phase after the plan commits. Dependency planning may resolve identities but cannot access live ports, issue commands, mutate the world, or emit events. Invalid or unresolved ids fail that source revision with a diagnostic. Unbarriered port access and Modelica event delivery fail visibly. Connected Modelica event edges are sampled only while the simulation clock advances, after SimTickSet and before Rhai; this gives an initially active output its producer tick after scenario readiness. USD simulation projection uses stable logical stage source, optional instance-root path, and authored prim path for bounded admission, topology-preparation order, and dynamic-body promotion; missing or ambiguous identities fault, remain queued, or stay kinematic. Physics operations that can accumulate into shared bodies use PhysicsOrderKey from the stable logical stage source, instance root, and authored prim path. Joint solving, motor warm-start, custom prismatic correction, raycast and jointed tire forces, and raycast mass-property folds consume stable key order. The production multi_rover_stress Rhai gate selects a profile from typed scene-test parameters and compares only the requested reference row at each sample. Rust loads the portable JSON at the --determinism-reference PATH process boundary and exposes typed values through the existing Rhai query bridge. Rhai owns profile matching and exact comparison (numeric_tolerance=0) for six physics checkpoints, selected Modelica and articulated checkpoints, and the explicit final physics, Modelica, and articulated state. The test keeps only checkpoint tick numbers and the first mismatch; it also verifies Rhai rejects a deliberately altered physics row and does not emit or retain a trace bundle. The Bash and PowerShell matrices in scripts/test-deterministic-physics-profiles.sh and scripts/test-deterministic-physics-profiles.ps1 invoke regular production luncosim test processes and trust their exit status. They leave --tick-hz unset, so the default fixed step is 60 Hz; the runner advances the manual clock without wall-time pacing. This remains fixture-specific evidence. Do not claim whole-simulation replay determinism while the remaining reviewed gaps are open.

Reflected commands sent through ApiCommandEvent retain whether they came from an API transport or a Rhai evaluation; generated CommandOccurred facts carry that origin. The workbench uses PanelCtx::trigger_command to carry an explicit local-user SessionId across deferred UI dispatch; the port inspector therefore enters fixed-tick input admission. Direct typed Bevy triggers without an explicit origin remain unclassified, and the occurrence fact still lacks command parameters, target, scene generation, effective tick, and per-tick input order. Rhai scenario origins carry their stable actor id and source execution sequence. SimulateIntentEdge preserves the complete origin through its typed semantic edge into the bounded CausalTrace query; Rhai records include owner route, phase, generation, sequence, and actor id. API, application-Rhai, and direct typed discrete edges receive a committed scene generation, next SimTick, and per-tick sequence, then reach the event bus from the fixed-step owner. Simulation-clock Rhai edges remain derived behavior and do not receive this external-input stamp. The queue is bounded to 4,096 pending records and clears on scene teardown; the trace is not durable replay storage. External held SimulateIntent changes share the bounded queue and per-tick order with edges. API, direct typed, and actorless Rhai producers provide a stable nonzero producer_id; Twin Rhai retains its route and stable actor identity. Held acknowledgements, telemetry, CausalTrace, and session records retain producer identity. drive_from_bindings captures physical ActionState<UserIntent> into a by-value PhysicalIntentFrame semantic snapshot. Admission requires the local input SessionId, target GlobalEntityId, and committed scene generation. The fixed-step owner stamps the frame with those identities, the current SimTick, and a sequence from the shared lunco-control-core::SimulationInputOrderAllocator before controller translation. The allocator resets on scene teardown so producers share one per-tick order space. Admitted external semantic payloads wait in lunco-core-session::PendingSessionInputs; core-session clears that queue on scene teardown. Its fixed-step coordinator validates and captures due records, then publishes typed commit events in shared sequence order while simulation time is running; a pause leaves inputs queued for the next running tick. The controller applies semantic payloads before physical input sampling. Raw-file runtime spawns also enter this commit boundary; the scene-command owner checks the current frame and catalog and inserts the reserved root identity before identity admission. Physical frames remain sampled at their consuming fixed tick through the same per-tick allocator. Missing facts or duplicate target/session order keys hold input with a structured runtime error; ordering does not fall back to Bevy Entity bits. An active SessionInputStream captures sorted canonical intent ids, admitted controls, lifecycle input-hold releases, and raw-file spawn records. ControlAuthorityChanged queues ReleaseControlInputs for the next fixed tick. At its ordered commit, the co-simulation owner clears controller-owned port holds and the controller clears held semantic intents; authored program setpoints, endpoint values, and physics state remain intact. The release writes no stop value. Twin policy owns any stop setpoint and issues explicit named SetPorts writes. Capture retains the ordered lifecycle record when active. Missing target, generation, tick, or order facts produce a runtime error and leave existing holds intact. Spawn records retain producer, correlation, stable scene-root and active-frame identities, original f64 pose, admission stamp, and reserved root id; acknowledgements expose that stamp. Document-backed SpawnEntity still authors only ApplyUsdOps into the Twin journal. Capture remains bounded and in-memory; baseline manifests, playback, and other typed commands remain open. The persistent canonical WorldGrid has deterministic content provenance, so the default active physics frame also has a stable GlobalEntityId.

Scenario actor compile submission, completion commit, and hook execution use the source-owned GlobalEntityId component directly; the Update-synchronized API lookup index is not an ordering source. A local-only host without that component uses its Bevy entity key only within the current World and has no cross-session replay identity. Author observable multi-actor ordering checks in Rhai scene tests; reserve Rust tests for the generic ordering and async commit mechanisms.

For file-backed documents, use PreparedFileBacked with the shared DocumentRegistry::open_prepared_file path when source parsing moves to a worker. The registry remains authoritative for path identity, dirty-document preservation, and lifecycle events. Owners fence results against the exact source/document revision before committing them. The default USD Twin scene uses this path for native USDA parse and persistent-source serialization; runtime sidecar restore and the browser worker transport remain separate open boundaries.

Twin teardown releases domain documents and browser preparations before the replacement is admitted. Reopening keeps logical source identity but receives a fresh load authority. Resolve authored dependencies through lunco-assets-core::asset_path::load_asset_path with the admitted live origin; retired origins fail before rebinding, and origin-free requests keep exact transport authority. Background file snapshots resolve logical source names and fence publication by scope and mount revision. See 61-scene-lifecycle-and-teardown.md.

Lifecycle work that changes authoritative scene state participates in lunco-core-runtime::SimulationProgress. Acquire with a typed owner/operation key, carry that identity through async preparation, and release it only after the owning terminal result has been committed. Scene load/restart/clear use SceneTransitionId; same-path transitions still have distinct identities. The coordinator advances the shared scene generation only for the matching successful transition and emits SceneTransitionCommitted. Scenario and other Twin-scoped cycle owners read that committed generation; do not maintain a subsystem-local scene counter or arm lifecycle work from a stale completion. Lifecycle hooks receive a discrete owner context keyed to the exact transition. For example, scene.time.select runs as Twin/Lifecycle/Preparation with no elapsed clock, and its typed result carries the same SceneTransitionId through application. The time owner ignores stale results and terminal edges after a replacement begins. One-shot policy inspection uses Application/Repl/Evaluation. Keep participant readiness and PhysicsHolds in their owners: they control local/world physics admission, while SimulationProgress controls whether the shared causal tick may advance. UsdSceneRuntimePlugin installs this shared resource when selected and holds active reference spawns on the mounted primary scene through closure preparation and live ECS projection. Preview and additive mounts retain diagnostics without pausing or faulting the primary simulation. A terminal primary reference failure retains its exact hold and publishes a path diagnostic plus RuntimeFault until scene teardown; inactive or removed, nonfaulted operations release their own keys. Surface the active wait reason through the existing status bus. Async data that changes authoritative physics, including a mounted DEM download/build, also holds an exact terrain entity key through the committed collider/oracle result; on web the hold spans the coarse preview and full worker result. The DEM bridge and readiness scan run in PreUpdate before SimulationAdmissionSet, including while a Twin manifest scan is pending, so the first eligible fixed tick cannot precede terrain admission. UI and presentation schedules remain live. Persistent edits to the mounted primary USD document hold one coalesced UsdDocumentProjection key from the observed registry revision through the matching ECS projection commit; disposable view-layer edits do not hold world time when their typed operation suffix is available; an unavailable suffix is conservatively treated as causal. Change detection admits UI/command edits in the SimulationProgressAdmissionSet after entity indexing and before the PreUpdate simulation-admission boundary, and admits edits issued inside a fixed Rhai/event pass in that lane in FixedLast. The fixed runner completes the current cycle, then preserves its remaining overstep until the projected revision is committed. Do not poll document contents on steady frames or release the hold from a stage-only cursor before ECS projection completes. Physics readiness then admits bodies and joints on fixed steps. An active USD Modelica participant remains readiness-held through its first successful communication point; a compiled, intentionally paused model is ready without being stepped. Keep that participant readiness separate from the shared SimulationProgress lifecycle gate. First-compile intent is admitted by request_modelica_compiles in the lifecycle cycle while solver stepping stays in FixedUpdate, so compilation can be requested while virtual simulation time is held. The Modelica execution owner consumes typed CompileRequested intent, validates its pinned document runtime lifetime, and owns same-owner source gathering and worker dispatch; the UI command only chooses the class and publishes intent. On native, source-root installation, compile requests, Reset, parameter updates, and cache-invalidating Step auto-init share one single-owner Rumoca actor FIFO. Results return asynchronously and commit in submission order; actor requests and solve preparations use bounded admission. A Step that requires a rebuild resumes only after the matching session and library generation commit. Compile results must match both worker session and captured document generation. A result for an edited source revision is discarded, and an active model keeps its compile-run intent until a current result commits. The execution owner reconciles active causal models into SimulationProgress before SimulationAdmissionSet and releases each exact entity key only after the matching compile result has been committed. Intentionally paused and noncausal models do not hold world time. Their first normal co-simulation step uses the per-step barrier after activation. The root USD loader composes the available dependency closure before publishing the stage asset, and scene admission ends after structural projection. CPU mesh construction stays on the presentation path. Keep owner-specific clock gates separate; production acceptance must still prove that the first authoritative fixed tick observes each dependency declared by the scene and scenario, independent of worker completion order.

For engineering requirements, keep the same split at the numerical boundary: SysML owns typed intent, units, normative tolerances, and requirement/ verification identity; USD owns realized geometry and standard scene facts; Modelica/Rumoca owns equations and continuous state; Rhai owns the selected mechanical relation, orchestration, and evidence; Rust owns only reusable, hot, type-safe f64 mechanisms. The authored assets/scripting/tools/mechanical_relations.rhai library is the extension point for CAD-like predicates such as distance, coincidence, parallelism, under/clearance, mirroring, and symmetry. Do not grow a Rust registry of relation names or product-specific checks.

Use the existing Rhai standard math surface for ordinary scalar operations and the native Rust bridge for f64 vector validity, dot/cross, clamped cosine, angle, and native component access. Keep native values in hot loops; arrays are only explicit interchange boundaries. Use typed predicates (f64_only, array_is, map_is, string_is, vec3_is_native) instead of string-based runtime type protocols. Numerical settings are explicit per dimension and are resolved once per report/solve; they do not replace a source-owned SysML tolerance or become a global epsilon.

For Avian-backed physics, keep one numeric admission contract at lunco-physics::avian_backend. The BigSpace bridge owns lifecycle admission of f64 poses and collider support geometry before the Avian step, GridSpatialQuery reuses the converted-ray predicate, and USD projection reuses shape/AABB and leaf structure predicates before ECS insertion. Convex colliders are checked by their actual support points; composite or non-convex shapes use the conservative AABB boundary. A failed live invariant raises the shared scene-scoped runtime fault and gates the remaining nested physics phases. Do not turn these checks into per-call query fallbacks or lint-only warnings.

Rhai task callback contract

Task leaves accept anonymous closures (|me| ...) or named script callbacks (Fn("name"), declared fn name(me)). me is the host entity id; both forms receive persistent state as the driver-bound this. The native task driver owns progression and state transfer. Rhai map parameters are value/copy-on-write values: a helper that assigns a state field must return the updated map, and the lifecycle callback must assign it back to this. Do not rely on helper side effects to persist task state.

Plugin and crate layering

Use a domain CorePlugin for headless-safe state, lifecycle, commands, and runtime mechanisms, then add a separate UI plugin only for panels and visual presentation. Do not create a UI plugin merely to host API queries. If a provider belongs to a core domain but importing the API crate would create a dependency cycle, put the provider in a small *-api adapter crate and have each API-capable composition root install it explicitly. Keep the data/core crate independent of transport and presentation layers.

For dynamic providers, keep lunco-hooks as the single reflected contract and invocation owner. HookInvocation carries the typed runtime context with the validated values. lunco-hooks-plugin-api owns the stable edition-2024 ABI v2 and typed invocation wire helpers; lunco-hooks-native owns libloading, unsafe admission, callback limits, and exact-registration teardown. Enable that path only from an application composition feature. A Twin must explicitly approve a provider with a Twin-relative [[native_plugins]] manifest entry. Reject rooted, volume-relative, traversal, and alternate-stream paths; resolve through the asset owner's canonical containment check before loading. USD and Rhai source do not load shared libraries. Native code is trusted process code, so untrusted bundles require deployment-level signature and isolation controls.

A provider may implement only an existing reflected, installable hook and must return typed data or a validated action plan. It must not mutate ECS/USD or add a second domain registry. The owner invokes it through lunco_hooks::invoke with a typed HookInvocation: cycle-owned callers supply their runtime context, while discrete boundaries explicitly use an unclassified context. The owner validates the result and reports a fault or unconfigured state according to the hook contract. Policy manifests may mark an optional feature-owned hook with skip_when_hook_unavailable; the runtime reports that capability in policy_status().unavailable, while compile and activation failures remain in failed. Put an eventual expensive terrain provider at the consumed lunco-terrain-bake kernel boundary; keep lunco-terrain-core projection-free and leave lunco-terrain-surface as the runtime projection owner. See native-hook-providers.md.

Keep stateful per-entity Rhai scenarios on their owning Simulation lane. For a stateless Rhai decision at another cadence, prefer an existing owner-invoked hook with typed facts and context over adding more lifecycle callbacks to the scenario runtime. Cross-cycle results return as typed owner actions; a scenario this map never moves between cycles.

Keep the workbench split at the dependency boundary: lunco-workbench-core owns renderer-independent panel/menu/perspective/registration contracts, tab navigation, source-view commands, scene display state, pending tab-close state, and the published WorkbenchSnapshot; lunco-workbench-widgets owns shell-independent egui controls; lunco-workbench owns egui_dock, bevy_egui, viewport rendering, persistence, source editing, and command observers; and lunco-workbench-guided-ui owns the optional Rhai-driven HUD, spotlight, coach-mark, and guided-recovery surfaces; and lunco-workbench-browser owns the optional Twin/Files panels, browser state, and built-in filesystem/library sections without depending on the concrete shell. Rename payloads belong to lunco-doc-bevy or lunco-workspace according to the identity they address; the shell retains only picker/execution observers. Domain UI crates implement contracts from the core crate, read layout facts from the snapshot, and depend on the concrete shell only when they use those presentation services. Do not expose or consume the shell's private WorkbenchLayout outside that crate.

Keep application-edge presentation separate from the reusable shell: lunco-luncosim-presentation owns the final status/environment, terrain-horizon, USD camera/light, capture, and scene-presentation bridges, while lunco-luncosim-ui owns window/plugin composition and installs that package at the boundary. Native updater startup and its rendered surface belong to the optional lunco-updater package, not to the ordinary UI closure. Celestial body projection and cadence belong to lunco-celestial-spatial; ordinary moving scene objects use standard USD timeSamples through lunco-usd-bevy-animation. Do not create a mission-only trajectory component or clock when composed USD animation expresses the motion. Celestial state uses the one lunco-time::CelestialTime sample, an affine child of WorldTime. It drives ephemerides, body rotation, solar irradiance, lighting, shadows, geometry queries, and target positions consumed by the generic EnvironmentProbe direction resolver. SunState contains irradiance; it is not a parallel direction source. A rate up to 100,000× leaves Avian and Modelica at their ordinary fixed-step cadence. From lunar ground, Earth stays near one sky position because the Moon is tidally locked; its axial spin still advances the day/night pattern. BigSpace propagates both the changed Earth grid pose and rotation to GlobalTransform; one cadence gate commits the CelestialTime sample it read. Static authored-light rays are selected only after the active composed root's celestial projection has classified its source ownership. A celestial root owns the finite body target exclusively; any static ray seeded before classification must be withdrawn before probe resolution. The celestial hierarchy must not wait on interactive input bindings: invalid bindings may withhold the optional observer camera, but must not suppress the finite targets consumed by physics and Modelica.

Runtime scopes, cycles, and publication boundaries

Use the shared lunco_runtime_context::RuntimeScope and lunco_core::RuntimeCycleSet vocabulary when placing a cross-cutting system. Core, Application, and Twin describe ownership; Lifecycle, Simulation, Interaction, Command, Repl, Telemetry, Ui, Presentation, and Visualization describe cadence. These are schedule labels and typed route metadata, not a new global event bus. Twin-owned resources and completions must carry their mount generation and be retired at Twin teardown.

Cycle labels do not create independent clocks, CPU isolation, or execution cadence. Rust plugin composition decides which capabilities a host installs; Bevy's owning schedules provide the actual execution boundary. A typed RuntimeExecutionContext carries the current owner route, cycle, phase, clock sample, logical sequence, and optional event producer stamp into synchronous Rhai calls. Scenario hooks and one-shot REPL/tool calls use their owning contexts: RunRhai runs as Application/Repl, while typed UI tool callbacks run as Application/Ui after picking in PreUpdate and before fixed simulation. execution_context() exposes a read-only Rhai map. Generic hook calls carry HookInvocation; nested invoke_hook forwards its active context, and an isolated Rhai hook reads it from the immutable runtime_context map. The shared hook registry rejects a clock that does not belong to its cycle, missing or invalid clock samples, and non-zero Core/Application generations before invoking either Rhai or native policy. twin.lifecycle uses the mounted Twin's TwinId as its Twin/Lifecycle generation, with explicit Start, Event, or Stop phase and no elapsed clock; policy_status().lifecycle.runtime_context exposes the exact stamp. Rust call sites without a classified owner use the explicit unclassified hook entry point. The physics escape policy is a scheduled core simulation hook and receives its Behavior context from SimTick and Time<Fixed>; its authored policy rejects off-cycle calls. The scheduled readiness.action policy uses Core/Simulation/Behavior with Time<Fixed> and the latest SimTick, and its authored policy rejects calls from other cycles. sim_tick(), dt(), and elapsed_seconds() reject calls outside the simulation cycle as invocation errors, while missing mandatory simulation clock resources remain runtime faults. An unclassified invocation has no route; Rhai exposes its scope, cycle, and generation as unit instead of inventing an owner. Add a separate schedule driver only when a cycle needs independent cadence or overload semantics, and keep expensive calculations off the UI/physics-critical thread.

Native immutable preparation uses lunco_core_runtime::AsyncWorkAdmission; do not add another per-crate priority queue. Each request carries its scope generation, stable owner identity, source revision, and operation id. Priority selects which queued job starts; the owner still validates and commits its typed result at its own boundary. WebAssembly's Bevy async-compute pool runs cooperatively on the browser main thread, so expensive web work needs an explicit worker transport rather than this native dispatcher. Native terrain cover analysis and streamed tile-mesh bakes share this admission. Tile bakes use Interactive priority and a stable owner-selected nearest-first rank; the terrain owner fences completions by operation key and publishes meshes in stable coordinate order under its frame budget. Surface changes and owner removal withdraw queued work, and already-running stale results are discarded. Keep the browser tile-cache path explicit until Web Worker transport is available. Serialize each Modelica step's input assignments in variable-name order; never let hash-map iteration choose a worker command's observable order. File-backed Rhai source assets are parsed and const-folded by the asynchronous asset loader, which publishes their canonical id, exact text, AST, and literal import dependencies together. Activation owners commit the complete loaded dependency graph before binding or starting a source; Bevy can report graph readiness before its Added messages are consumed. Startup/Twin tools and the prelude reuse that AST; scenario workers reuse it only when the source bytes match, and parse inline roots or uncommitted sources inside shared admission. Do not compile loaded tool or prelude source synchronously during registration. The asset publisher retains each loaded AssetId's canonical URI and retires it on AssetEvent::Removed, because AssetServer::get_path may fail after the last handle is released. Ignore the earlier Unused edge, and prevent a stale asset removal from deleting a replacement source with the same URI. Twin SysML analysis follows the same boundary: twin.lifecycle selects the checked manifest source set, PrepareTwinSysmlAnalysis loads source assets and prepares one immutable snapshot through shared admission, and runtime twin:// queries read only the current Twin-id/root/operation result. Pending, failed, and unprepared snapshots remain visible. This read-only active-Twin work uses Interactive priority and never holds SimulationProgress; a scenario that requires SysML facts declares the typed owner key in its dependency plan. Open SysML documents use the same bounded Interactive admission. Lifecycle events capture immutable source/origin facts, and SysmlDocumentAnalyses commits only for the exact current document generation and origin URI. InspectSysmlDocument returns analysis_state as pending, ready, or failed; pending diagnostics and error fields are null. Rhai editor verification must return a retryable pending result and must not call it clean. These editor analyses do not hold simulation progress. Browser worker transport is explicit and remains unsupported by this native dispatcher. The Twin analysis producer registers sysml.twin-analysis and publishes each mounted Twin's state under its exact name. A scenario that needs those facts lists #{ owner: "sysml.twin-analysis", identity: twin_name } in required_inputs; only that scenario's activation hold waits for the async analysis. The analysis worker itself remains read-only and never acquires a world-time hold. Telemetry samples are captured with the fixed tick, then delivered through the plugin-owned bounded Telemetry cycle. Never run subscription, retention, or logging observers inline with fixed physics; report queue loss through the telemetry status query and keep simulation progress independent. The GUI installs TerrainSurfaceVisualizationPlugin; server and scene-test compositions keep terrain physics/query support but omit camera-driven LOD, visual-map baking, and overlays entirely. A system hidden behind a server-mode run_if still exists in that schedule and is not equivalent to omitting it. Application builders install the selected capabilities automatically; authors should not assemble Bevy schedules by hand. Rhai currently declares peer-role selection through @peer host|client|both, with simulation as its supported timing. An unknown execution target, unsupported timing, or unknown metadata directive disables only that scenario and publishes one document error for its source generation instead of breaking the host, defaulting to host, or guessing a clock. Cycle and clock selection come from the Rust owner, not a script directive. A callback error remains visible and local to its owner; required authoritative hooks hold/fault their owner. Never panic or silently report success for a failed hook.

The initial camera policy receives Application/Presentation/Preparation with the Time<Real> presentation clock. Its RuntimeCycleSet::Presentation labels are ordering metadata inside Update; they do not give UI or visual LOD an independent cadence. The runtime UI recording selector receives Application/Ui/Preparation with the Application clock in its PostUpdate owner chain; it runs only when its revisions change, not as a separate cadence. The authored runtime-surface visibility/property policies receive typed Application/Ui/Preparation context from exposure publication; camera-status properties receive Application/Presentation/Initialization or Application/Presentation/Event from the camera-status publisher. Add a policy guard and verify both the scheduled owner context and an off-cycle call when assigning context to another hook. The route_lifecycle production gate checks ReadExposures for an authored program-browser surface after metadata admission.

The rendering-quality catalog receives Application/Presentation/Initialization on startup and Application/Presentation/Preparation for stale-policy refreshes, both using Time<Real>. The render shadow-warning policy receives Presentation Preparation context from its PostUpdate owner when its configuration changes. The Presentation cycle set remains ordering metadata and does not add a separate cadence.

The event-driven usd.component_refresh owner supplies Twin/Lifecycle/Preparation with the active or committed generation for a mounted-Twin document, and Application/Lifecycle/Preparation for a preview- only document. The owner requires a generation for a mounted Twin; a missing generation rejects that policy-controlled refresh visibly.

Cycle labels identify ordering and ownership; they do not imply that every owner already publishes timing and queue metrics. Expose bounded aggregates at task boundaries where measurements are needed, then use them to target a Tracy capture and measure frame responsiveness in a separate unprofiled run. Do not feed a per-frame diagnostics firehose through the simulation telemetry sampler. CosimStatus exposes per-session Modelica solver-step duration, dispatch-to- response latency, and the native worker's pending-task count at step start. Pass include_values: false for a bounded fleet view without input/output maps or verbose model/error details; status, counts, and timing remain available. Rhai profilers can pass include_entities: false to read aggregate worker metrics without constructing a string-bearing row for every participant. Response latency includes queueing, transport, and owner response handling; it is not a pure queue-wait measurement. The browser worker does not expose its internal queue depth.

Keep telemetry's two lanes distinct: authoritative events used by Rhai retain their producer tick and deterministic delivery order; continuous samples are bounded observations of committed state. The fixed sample boundary captures due channels into a small typed record. A cached channel plan still requires a fixed-path walk and live port reads; its samples then go through a bounded post-simulation cycle. Logging, subscriber fan-out, formatting, serialization, persistence, and UI plot decimation run outside the physics transaction. Reducing fixed-path sampling work and measuring its effect on throughput remain open owner-level work, not a completed guarantee.

Commands and one-shot Rhai/REPL evaluations have independent application clocks. Record command cadence from the shared CommandOccurred publication and REPL cadence from actual drain_world_scripts evaluation. Both use the wall clock, never simulation time; a queued script is not counted as evaluated until the exclusive REPL owner runs it. Publish their sequence and timing through ApplicationCadence/application-cadence so consumers do not add their own timers or infer cadence from render frames.

World-bound one-shot Rhai requests remain serial because their verbs read and mutate the live World. The REPL owns a bounded FIFO (64 pending requests by default), evaluates one request per Update, and applies a 100,000-operation ceiling per invocation. Queue overflow is a terminal command error. Keep scenario and hook execution in their owning clocks; do not parallelize callbacks that still use live-world access.

Publish each fact through its one domain boundary before adding a consumer: scalar co-simulation endpoints use PortRegistry; presentation-ready values use EngineExposures; lifecycle occurrences use typed events or revisioned resources. Status bars, authored HUDs, telemetry adapters, API readers, and recorders consume those publications. They must not independently scan engine diagnostics, query a physics owner, or introduce a widget-specific registry. The fixed-step and rollback co-simulation paths share one propagation function and compiled cache; transform propagation remains a separate spatial owner. Rollback is an instantaneous replay cycle over recorded inputs, so systems in RollbackReplay must not be gated by the live virtual clock's paused/running condition. Keep its actuation ordering aligned with the fixed path and test the real replay schedule while Time<Virtual> is paused. This body prediction replay does not reconstruct whole-session commands, Rhai state, or Modelica state; do not claim whole-simulation replay determinism from it.

For a ShaderLook with vertex_shader, treat the fragment and vertex sources as one linked material contract: both stages read the same @binding(0) uniform block, so their Material fields, order, and WGSL types must agree. The fragment schema is the packed layout; do not invent a second vertex schema or silently select a replacement shader. Validate the requested stage positively: sourceAsset needs @fragment and vertex_shader needs @vertex; a combined WGSL module is valid for either role. A missing or invalid stage is a structured runtime diagnostic and an unbound material. Rust must not install a StandardMaterial, neutral shader, or another guessed source as recovery. If a scenario needs recovery, make it an explicit Rhai policy that can be replaced or disabled without rebuilding the renderer. Add a cross-file ABI test when maintaining a multi-stage shader pair. See shader-layers-and-params.md.

Treat an illumination-bearing grayscale orthophoto as measured imagery, not intrinsic reflectance. The native lunco-assets-processing kind = "albedo" pipeline removes its low-frequency illumination field, anchors local detail at a neutral material base, and sRGB-encodes the resulting linear colour for the 8-bit PNG contract. The texture loader decodes that material back to linear; terrain shaders use it directly and have no orthophoto compensation function. kind = "map" remains an analysis/display contrast map and must not be bound directly as inputs:albedo_map. When weight_albedo is authored, it owns terrain colour variation; scale procedural dust/mottle by 1 - weight_albedo, while keeping relief normals, roughness, ambient occlusion, and photometry independent. Heavy raster math stays in Rust; Rhai assembly policy selects and authors the standard USD material inputs. The packed surface map's G channel is ambient occlusion: route it through the shared terrain_surface_occlusion helper into Bevy's PbrInput.diffuse_occlusion, never into base albedo. AO is indirect-light visibility; multiplying it into albedo creates broad false colour patches and darkens direct sunlight. The render-side ShaderLook binder owns event-driven preparation of filterable authored RGBA8 maps: it deduplicates off-thread mip generation by image asset version, filters colour in linear light, averages scalar maps linearly, renormalizes normal vectors, and then enables trilinear/anisotropic sampling. Do not skip a zero-weight map at load time: authored weights are live inputs, while mip preparation is the separate renderer-owned image lifecycle.

Project-owned persistence policy belongs to the active Twin manifest's generic settings boundary. A domain may define one namespaced scalar key and expose it through the existing SetTwinSetting path; it must not add a global settings section or a second cache reader/writer for the same artifact.

The local avatar is a runtime kinematic camera embodiment. Its collision movement uses Avian's existing MoveAndSlide query against standard UsdPhysics colliders in ActivePhysicsFrame; it does not need a second USD body schema or a separate collision representation. An explicit Twin setting may select a documented unsafe policy, but the movement owner reads that setting directly and remains safe when the setting is omitted, malformed, or the Twin closes.

The shared semantic input contract lives in lunco-control-core, while the persisted device-to-intent map lives in lunco-input-core, and is not avatar-only: the workbench owns one app-level local intent surface for editor actions when an isolated preview has no avatar. Shared actions such as CancelIntent read that surface through the same InputBindingsSettings map, while avatar control continues to use its own surface; neither path may introduce a raw-key or duplicate binding. InputBindingsSettings::default() is a valid neutral override: it has no key or pointer overrides and uses the schema-defined Right look button until the application overlays its authored defaults. The authored document remains the source of product bindings, and persisted explicit values take precedence. An explicit invalid runtime value must be rejected, diagnosed in the status bar, and clear every live semantic input map so a stale map cannot continue controlling the simulation. The Workbench host remains alive with input disabled until settings are corrected. Invalid persisted sections are removed with a settings warning before the authored defaults are installed.

Source-backed program attachment

AttachProgram is the one authoring boundary for binding a .mo, .py, .rhai, or behaviour-tree source to an existing USD prim. Rust owns the generic command and lowers a complete ProgramAttachSpec into one journalled USD change set. The spec carries the source asset, explicit scalar inputs and outputs, defaults, native USD connections, and the explicit realtimeSafe promise.

The Models palette, Rhai assembly_edit::attach_program(...), HTTP callers, and the Assembly editor all use this command. None may insert an ECS marker or maintain a second program registry. An empty port contract is a valid source-only attachment, but it is not a running scalar cosim participant; the author must declare the interface before wiring or stepping it.

Generic authoring evidence

Authoring review is a Rhai policy over generic typed substrate. The shipped authoring_inspection library owns candidate comparison, diagnostic grouping, diagnostic navigation, unified inspection records, and inspection-mode policy; it does not know about rovers, landers, or other product nouns. Rust only provides the reusable mechanisms: document-scoped QueryUsdPrim and InspectUsdDocument reads, exact preview selection/framing, generic SetDiagnosticLayers, and settings-backed view-only camera presets.

Keep the ownership split explicit:

  • USD remains authoritative for identity, topology, standard visual/collision facts, joints, frames, materials, connections, and provenance.
  • Rhai chooses the affected paths, groups findings, decides which diagnostic layers to show, and composes the evidence record for a human or AI review.
  • The Editor owns selection and preview leases; FrameUsdPreviewSelection frames one exact composed path and must not fall back to a whole-stage frame when the requested path is unavailable.
  • Shared settings own persisted view-only camera presets. Presets never become USD camera prims, journal entries, or another scene graph.

Use exact DocumentId, UsdPreviewId, UsdPreviewViewId, edit target, and projection generation at every boundary. A document-local Editor fork may be queried through its composed document stage even when it has no mounted Twin projection; a stale mapped Twin document must still fail visibly. Missing collision or provenance is an explicit diagnostic record, not a fabricated default. Keep positive and negative contracts in the production Rhai scene gate; do not duplicate these observable assertions in Rust unit tests.

Document-private viewport mounts are recorded separately from workspace/preview leases in DocBackedTwinScenes. Release them at DocumentClosed, or when the final preview lease ends with no workspace projection owner. The projection synchronizer must not consume that closure bookkeeping. Catalog mount-set changes also prune private authorities before replacement scans.

Show full SKILL.md (3,942 more words)Show less
Shared asset catalog discovery

Asset enumeration belongs to lunco_assets_runtime::discovery and runs through the shared asynchronous catalog listing owned by lunco-scene-catalog. USD, WGSL, Modelica, and Python projections are published from one root snapshot; they must not add a second filesystem walk or a UI-thread scan. A new manifest/Twin snapshot advances the listing generation, reopens the USD read set, and drops older metadata completions. This keeps a Twin opened during an initial scan complete without allowing stale work to populate its catalog. TwinClosed retires closed-mount metadata, spawn and shader choices before replacement. It invalidates pending listing/metadata generations while retaining engine and surviving-mount entries; late completions cannot republish a retired source.

Asset provisioning follows the same dependency split: lunco-assets-datasets owns declarations and lifecycle state, lunco-assets-transport owns native HTTP retry/resume, lunco-assets-download owns verification/extraction and atomic installation, and lunco-assets-processing owns native decode and baking. lunco-assets is only the explicit Bevy worker/CLI composition root. Its ProcessorRegistry selects heavy Rust implementations by authored ProcessConfig.kind; Rhai owns dataset selection and sequencing, with extra processor values carried in process.parameters. Do not grow a central Rust match or make ordinary runtime readers depend on this provisioning stack.

For derived 3D annotations such as a waypoint route, keep one reusable presentation tool between authored/runtime facts and presentation consumers. Resolve authored identities through the authoritative binding map and write the derived USD view to @view@ only when the route changes or the view is first materialized. Keep durable route edits in @runtime@, separately from the source scene. Use standard USD geometry and the existing renderer for depth and occlusion; do not create a Twin-specific ribbon prim or a per-frame document edit. A reusable route may be a separate composed USD plan; the tool follows the selected program's canonical parent scope rather than assuming /Route. The disposable view must use the typed transient USD projection command, so it is generation-checked and OpenUSD-backed without entering authored undo, save, or Twin journal history. Marker-root placement, annotation geometry, labels, and look remain separate owners. Stable frames must do no route parsing, binding lookup, mesh generation, or marker writes; camera-dependent label projection is the only remaining per-frame presentation work.

Terrain strokes use producer-owned segment IDs and the mutable lunco-terrain-surface index. Ordinary edits publish dirty texels through lunco-materials::float_texture and the renderer's partial-upload adapter; capacity growth follows the shared image descriptor and material refresh path. See docs/architecture/50-usd-driven-visuals.md for the ownership and lifecycle contract.

The same boundary applies across document domains: user Rhai/Modelica edits use their DocumentHost operation path, while file-backed, USD-embedded, and generated source refreshes use the shared FileBacked::reload_base contract. External refreshes advance the live generation and invalidate consumers but do not create a second undo/journal entry. Never replace a host to hot-swap source; that discards history and breaks the canonical document identity.

Failure-path acceptance must use an owner-local, transient fixture or typed test command. For example, the Scenarios menu may inject its unavailable presentation state without removing or replacing TwinRoots, the active Twin, or scene data; the default production path remains unchanged and the detailed cause still flows through the shared StatusBus.

Repeated presentation solvers must also be value-idempotent: compare derived Transform/CellCoord values before assignment. Bevy marks mutable component access as changed even when the value is equal, and BigSpace consumes those signals for dirty-subtree propagation. Guarding an equal write at the producer is part of the ownership contract; it is not permission to hide a real dirty input or to add an alternate propagation path.

Use the same owner-local revision/cursor shape for other stable projections: the Modelica document registry wakes engine sync, telemetry producers pace by their authoritative model/fixed time and capture a live GlobalEntityId in the render-free SignalRegistry before a source can become archived, behavior target paths are cached per entity and invalidated by authored XML or active-frame ancestry, terrain curvature reacts to its input components, and globe LOD caches pure selection until camera/LOD/handoff/residency inputs change. These cursors suppress work; they do not become a second data source or a compatibility fallback.

Apply this contract to all camera pose owners, including shared interaction easing, mounted USD followers, cinematic path followers, and the persistent camera origin. Camera selection/mode policy stays in the application; BigSpace owns only precision representation and derived transform propagation.

Celestial body frames, the sky-time readout, and celestial geometry queries use CelestialTime. Terrain, stations, and links remain attached to their physical body frames, which follow that same sample. Unbound USD animation uses the interpolated physical-time sample. Keep each station and marker on the one body-fixed grid; do not add a parallel presentation grid or marker copy.

Scenario telemetry is collected only while ScenarioExecutionGate is open. That gate waits for all initial scene readiness holds because scenarios may reference entities outside their owner subtree. After admission, entity holds idle only scenarios inside the held owner subtree. Fixed-step delivery after SimTickSet releases only events stamped before the current tick; paused Update delivers discrete events without advancing that tick. A new scenario reads current owner state in on_start rather than replaying events from before its lifecycle. Clear the outgoing scene's pending batch when a scene transition closes the gate.

Workbench perspectives publish scene visibility as layout intent. A perspective that uses the full window as its 3D presentation must opt into Perspective::scene_visible_when_docked() so opening a transient side or bottom panel does not deactivate the selected scene camera or paint the themed backdrop over the whole frame. Central editor perspectives remain hidden unless their slot intent includes ViewportPanel; the Workbench still never writes Camera::is_active, and lunco-usd-bevy remains the sole camera reconciler.

Temporary diagnostic visuals

Temporary camera/collider/dynamics diagnostics are runtime presentation, not USD facts. Reuse the existing Gizmos systems and one Twin-scoped DiagnosticVisualLease store: Rhai/API/UI selects an explicit target and policy, while Rust resolves SceneViewport, StageView, Avian collider realization, BigSpace render poses, bounded snapshots, and lifecycle cleanup. Do not add a diagnostic USD schema, temporary physics entity, per-frame USD edit, second camera selector, or global name-only registry. Handles must carry the Twin/root/mount generation and become stale on SceneTeardown, reload, target deletion, or TwinClosed; missing targets and unsupported shapes are visible errors. The draw pass reads finalized render transforms and never writes physics or scene state. Existing separate debug toggles must converge on this lease boundary when the feature is implemented rather than gaining another toggle API. See docs/architecture/temporary-diagnostic-visuals.md.

Assembly document snapshots

Assembly editing starts from the existing document system. Use DocumentRegistry::fork and the domain's ForkableDocument implementation to make an untitled document with a fresh identity; do not create a second scene model or copy a registry. The document implementation copies authored layers and invalidates private derived state, while DocumentHost copies undo/redo history by value. The registry attaches a recorder for the new id to the same Twin journal. Save-As is the first path binding. A derived cache must be document-owned and keyed by all authoritative layer revisions; full USD composition and dependency resolution stay with lunco-usd-compose and its existing resolver path.

Native Assembly Editor view-models are keyed by the existing UsdPreviewId session. Derive one prim tree, connection canvas, Inspector subview, and authored USD subview per open session, then paint the session selected by the focused UsdPreviewViewId. Views share the projected stage but own their camera/render target and never become document identity. Hidden view cameras remain inactive, and visible targets are bounded by UsdPreviewRenderBudget (2048 px per axis, 4,194,304 pixels per view, and 8,388,608 visible pixels per frame by default). The shared ECS selection is only the focused-session projection; keep selection and drilled targets in editor-owned session state so focus changes cannot apply a command to a same-named prim in another document. Always carry the session's explicit DocumentId, LayerId, and projection generation into a typed USD command. The editor's path-selection command is SelectUsdPrim: it requires the focused UsdPreviewId and resolves through that lease's stage and preview-root hierarchy.

When the work is an agent-driven human asset edit, use the interactive Assembly Editor runbook. The authoring session is headful and remains visible to the user; every coherent change is applied through the existing typed/journal path, inspected through the focused preview and a screenshot, and reviewed with the user before the next material change or save. This is an operating mode over the existing ownership model, not a new assembly API.

The render-side camera binder also owns Bevy's clustered-light policy. Use Bevy's ClusterConfig::Single for automatic cameras while the ECS topology has no point lights, spot lights, light probes, or clustered decals, and follow those component lifecycle events back to Bevy's normal configuration when one appears. The camera reconciler must also wait for a positive computed viewport and positive Clusters dimensions before activating a new window camera, because GPU extraction receives active cameras before the first cluster assignment. Preserve an explicit ClusterConfig; do not add a scene-name check, per-frame light scan, or alternate lighting implementation.

Light shadow intent follows the same standard-schema boundary: read UsdLuxShadowAPI.inputs:shadow:enable from the composed stage for every light. Application possession or graphics settings must not overwrite that authored intent; renderer-owned resource budgets publish facts and let the authored Rhai render policy report unmet limits, never suppressing authored casters at the admission boundary.

Scene-root UsdPrimPath values may be empty until the stage is parsed. Resolve that sentinel through the shared USD defaultPrim resolver before any domain projector reads the path; visual and celestial projection must not each invent their own deferred-path handling or permanently mark an unresolved root.

Start with the standard-schema audit

Before creating LunCo*API or a lunco:* property:

  1. Inspect the vendored OpenUSD schemas in crates/lunco-usd-document/schema/core/ and the maintained USD/PhysX schema that actually owns the concept.
  2. Use the standard field when it exists: UsdGeom for transforms, geometry, cameras, visibility and purpose; UsdShade for connectable graphs and materials; UsdLux for lights and shadows; UsdPhysics for bodies, mass, collision, joints, limits and drives; USD metadata and assetInfo for descriptions and asset identity; USD connections for graph edges.
  3. Add a LunCo schema only for semantics that have no standard owner, such as mission/celestial meaning, engine program allocation, a LunCo-specific sensor configuration, terrain generation policy, or control-session ownership. Keep that schema narrow and do not duplicate standard fields.
  4. If a custom field overlaps a standard field, migrate every reader and asset to the standard spelling in one change, delete the superseded field and branch, and regenerate the schema artifacts. Do not read both spellings.

The mapping and the current keep/remove decisions are recorded in references/usd-standard-map.md. The authoritative engine architecture is docs/architecture/clean-architecture-and-usd-standards.md.

Classify a gap before changing the engine

When a report says a capability is missing, verify it against the target checkout before accepting the claim. Search the shipped asset library, reusable Modelica packages, composing scenes, and authored tests; read the closest production exemplar and its consumer. Classify the result as:

text
engine substrate | reusable asset | mission assembly | external dependency | evidence gap

An absent mission asset is not an absent engine capability. A source parse or documented feature is not runtime evidence. Record the exact source path and the strongest observed state separately: parsed, composed, contract-ready, solver-running, numerical behavior, or visual behavior. Only an infrastructure gap that survives an authored fixture justifies a Rust design.

Build a reusable component

  1. Put reusable geometry, physics, parameters, and Modelica source under assets/; keep scene-specific opinions in the composing scene layer.
  2. Give a component one clear default prim and kind = "component"; give a composed vehicle kind = "assembly". Use doc, displayName, assetInfo, UsdGeom, UsdPhysics, UsdShade, and UsdLux before adding namespaced duplicates.
  3. Represent real attachment with a USD physics joint and its authored frames. Hierarchy is namespace, not attachment. A mounted rigid body without a joint is a free body.
  4. Encapsulate each actuator or sensor as a reusable asset. Instantiate and connect it in USD. The lander model must not know that a component is called an RCS engine, reaction wheel, or altimeter; it consumes local-frame ports.
  5. Let Avian apply force or torque at the authored actuator frame. Do not add a special Rust emitter for one actuator family, convert to world coordinates in Modelica, or maintain a parallel actuator registry.
  6. Expose tunable physical values as typed USD-authored parameters or Modelica parameters; do not hide them in Rust, Rhai, or a renderer. Named constants are appropriate for policy-owned presentation geometry, spacing, extents, and typography when they are clearly separated from physical parameters.

Build a sensor and controller

  1. Expose raw built-in Avian observations through generic ports. Rust may know that a ray hit happened and publish distance, validity, normal, point, and relative velocity; it must not decide that the observation is an “altimeter” or “landing sensor”.
  2. Mount the sensor and author its connections in USD. Keep sensor placement, frames, collision filters, and parameters in the USD asset.
  3. Convert raw observations into useful navigation signals in Modelica. Put filtering, derivatives, frame conversion, attitude reference, PID, thrust mixing, fuel, and actuator dynamics in Modelica. Modelica uses local frames; it does not receive a hidden world-coordinate special case.
  4. Treat the parsed Modelica contract as authoritative. A compile-time parameter is not a runtime input. USD defaults for parameters go to the parameter set; only actual Modelica input variables enter runtime wiring. A missing required port is an authoring error with a diagnostic, not a fallback to an alternate name or a fabricated zero.
  5. Wire the model's outputs to generic body/actuator ports through native USD connections. Use the same path for autopilot, API, and scenario writes.
  6. Use possession and an authored authority signal for manual handoff. Do not create a second manual flag or bypass the control surface.

Generated Modelica networks

Generated models are a first-class reusable composition boundary:

Root and island rule

One CollectionAPI:components network root produces one generated Modelica participant with one public boundary. The synthesizer may partition its graph into several generated Modelica units, but those units remain inside the same root and are not additional ECS participants.

After validating a generated source, publish it with its parsed interface and link it to the normal Modelica document. Lifecycle compile admission dispatches the compile once that document is current, using the same generation/session fences as authored models. USD projection must not send a direct worker compile.

Scene lifecycle projection, stable entity identity assignment, and API/path index publication run in that order in PreUpdate, before SimulationAdmissionSet. ClockProjectionSet gates the following virtual delta sample, and the fixed runner checks admission before each full tick. Do not add a separate startup identity pass or readiness poll; a projected reference must resolve by path on the first resumed tick.

Keep acausal conservation connectors (Pin, HeatPort, FluidPort, Flange, and equivalent domain connectors) inside the root whose solver owns their algebraic equations. A typed scalar USD connection between two roots is causal and may have a macro-step or one-step delay. It is not an acausal connect().

USD connection projection caches immutable endpoint facts by composed-stage generation and runtime-instance identity. Reconciliation retains unchanged SimConnection entities and bindings; edited edges use the normal add/remove lifecycle. Endpoint lifecycle observers, USD edits, and authority changes feed the shared UsdWiringDirty latch, so stable updates do not scan endpoint populations for Added<T> filters. Domain source-class completion invalidates only roots that use that source. A root is not synthesized until every referenced member class has a terminal verdict, avoiding repeated graph extraction across asynchronous asset arrivals. Scene teardown clears the scene-owned reverse index, pending candidates, and in-flight synthesis tasks; resolved member-class facts remain asset-owned and reusable. Cosim source discovery and Python readiness share one lifecycle-coalesced pending-prim set rather than probing all USD prims for unprocessed markers on stable updates.

Synthesis hooks run with the owning Twin/Lifecycle/Preparation context and active-or-committed scene generation, without an elapsed clock. Capture that typed context before async dispatch and pass it unchanged to the hook; the synchronous live-projection path uses the same contract. Fence async results by that Twin generation and the owning USD generation or prepared plan. Keep the policy's context check in Rhai and cover off-cycle rejection plus generated-source publication in a production scene test.

If zero-delay bidirectional coupling is required, move the coupled components into one generated Modelica root and solve the combined DAE. Do not add a Rhai polling loop, duplicate state, or hidden cross-root fallback. Automatic island fusion is an optional future mechanism; correct network authoring is the current default.

  • USD owns the component graph, instances, port names, parameters, and connections.
  • The registered generator emits a normal, inspectable Modelica model with stable component names, policy-owned unit instance names, and explicit boundary inputs/outputs; runtime-generated documents are read-only projections of the authored USD + Rhai policy.
  • The generator is selected by an open domain descriptor/registry, not by a Rust if for “electrical”, “hydraulics”, or one vehicle.
  • Acausal equations and physical conservation stay inside Modelica. Causal cross-domain signals cross the USD boundary as typed ports.
  • Compose reusable Modelica classes through USD before introducing a vehicle- or mission-specific .mo wrapper. Add new equations only when the maintained package and its public contract cannot express the requirement.
  • Render the generated Modelica icons and connection graph from the same model source; do not create a second visual-only network.
  • Make the generated browser entry useful on first click: a single-unit network opens its unit class so the member graph is visible, while a multi-unit network opens the root wrapper. Keep both classes in the ordinary Modelica source/drill-in hierarchy; this is a navigation choice, not a second generated graph.
  • Keep generated visual synthesis in the selected Rhai policy: standard root / unit Icon and Diagram annotations, policy-owned placements, and any domain-specific presentation belong there. Rust may provide generic source loading, class resolution, and typed projection metadata, but must not encode a domain poster or duplicate the policy's graph.
  • For a power-network policy, make common-bus semantics visible with standard Modelica Line waypoints and a policy-owned diagram rail. Use adaptive, extent-aware placement for repeated members. Derive the visual hub from graph incidence, using the typed LunCoModelicaTopologyAPI storage role only to break equal-incidence ties; place source and load roles on opposite deterministic banks and pack neutral members onto the shorter bank. Do not branch on component class names or let a fixed demo layout imply a direct source-to-load wire when the composed graph has many members. These roles are presentation metadata, not Modelica solver direction: acausal flow can reverse at runtime. Keep source/load lane ranges disjoint around the hub so a horizontal route cannot imply a direct connection. Member coordinates are local to the owning unit diagram; root coordinates place unit instances.
  • Reuse the generic Modelica flow animation for electrical networks. A native flow Real such as LunCo.Electrical.Pin.i must be discovered by connector metadata and sampled from live node state; Rhai emits ordinary Pin/ connect(...) equations and must not grow a generated-electrical animation branch. Non-zero signed flow animates in the resolved direction; zero or missing state remains idle/diagnostic.
  • The flow renderer reads all declared connector flow variables and live runtime state keys, not a domain-specific value or generated policy field. Precompute lookup keys during projection and keep the per-frame walk linear in route segments plus visible dots.
  • Keep generated browser metadata explicit: distinguish root boundary inputs and outputs from promoted member telemetry, and expose the generated document as read-only runtime state with a normal Modelica drill-in path.
  • Keep editing semantics honest: an editable Modelica document moves nodes by emitting the generic canvas NodeMoved event and persisting standard Placement annotations through ModelicaOp::SetPlacement. A generated document stays read-only because USD plus Rhai owns its source; expose Duplicate to edit instead of accepting a non-persistent drag.
  • Keep projection responsive: Modelica root loading, parse, and inheritance/icon walks run off the UI thread. UI readers use completed caches or a nonblocking lock and show an explicit loading/error state until the generic completion event requests reprojection. Never hold the engine mutex across painting.
  • Validate the returned generated source as a generic strict AST contract: exact root name and boundary, required source, units, layout.units, layout.members, source_roots, and member_output_aliases fields, no undeclared root/unit causal ports, promotions only for outputs present in the loaded member class, non-overlapping policy placements, complete policy units, native members nested in their owning units, and no direct native members on the root. Member-placement overlap is invalid within one unit coordinate system; different unit diagrams may legitimately reuse local coordinates. Treat missing or loading class definitions as explicit resolver states in the canvas, never as a fabricated resolved node.
  • Keep generated document lifetime tied to the projection entity. Classify it by the generated/ document origin, retire it on removal/despawn, and keep authored document cleanup separate. Structured packages under assets/models/<Root>/package.mo and Twin-declared [modelica].paths are ordinary Modelica search-path roots: the compiler/editor discovers the root segment of a qualified reference generically and loads that package through the shared Modelica engine. A Twin without that section derives package roots from its indexed .mo files. Each live compile admits required roots through the LoadSourceRoot worker path before Compile; file-backed source assets derive requirements from their prepared AST interface, and generated-model policies return the required source_roots manifest. That manifest does not replace composed USD facts for class discovery, and Rust must not name a particular library. The live worker rejects unadmitted roots and reports failed roots to dependent compiles rather than synchronously rediscovering them. Reproject from the generic completion signal.
  • Let Rhai own the required member_output_aliases promotion table, including the explicit empty-table case. Rust may validate known member/output pairs and identifier uniqueness, but must not choose aliases or emit visual source for a policy.
  • Keep policy contracts in authored Rhai scene tests under assets/scenarios/tests/; reusable standalone assertions may remain under assets/scripting/tests/ and run through scripts/api/run_rhai_test.sh. The Rust host supplies composed facts and invokes the shipped policy; Rhai owns assertions about generated source, topology, layout, and presentation. Literal top-level Rhai constants are supported inside policy helper functions by the shared hook binding, so presentation policy can remain editable without adding Rust-side layout parameters.
  • A cyclic set of separately co-simulated components connected by typed scalar SimConnections is explicit causal feedback: the fixed-step exchange may have a one-step delay, but it is not an unresolved algebraic loop and must not emit an algebraic-loop warning. Do not add a Rhai polling bridge. If zero-delay continuous feedback is required, synthesize one Modelica network and solve it as one system; force-producing cycles remain subject to the explicit realtime-safety contract.

Replace a superseded contract cleanly

When a mechanism is wrong, perform a clean cutover:

  1. Identify the authoritative replacement and write a positive test proving the replacement contract first. Add a negative case only when rejection or safe failure is itself a public contract (for example, the removed spelling must be rejected at the public schema boundary).
  2. Update source assets, schema source, reader, projection, commands, and docs together.
  3. Delete the superseded property, alias, compatibility branch, fallback reader, and migration-only shim. Do not preserve an invalid contract for compatibility.
  4. Regenerate generatedSchema.usda and plugInfo.json from the authoritative source where applicable. Never edit generated schema by hand.
  5. Verify parse, composition, contract, projection, solver, and real executable behavior. --validate proves syntax only; it does not prove runtime wiring.

Permitted defaults are only semantic defaults declared by the authoritative USD or Modelica schema. A numerical guard such as a positive epsilon is not a compatibility fallback and must be named as a numerical guard.

Verification gate

Run the smallest relevant checks first, then the production binary:

bash
python3 scripts/gen_schema.py
RUSTC_WRAPPER= cargo fmt --all -- --check
python3 scripts/gen_schema.py
"$LUNCOSIM_BIN" test --scene scenes/tests/sensor.usda
RUSTC_WRAPPER= cargo test -p lunco-usd-sim --test usd_connection_mechanics -j 4
CARGO_INCREMENTAL=1 RUSTC_WRAPPER= cargo build -p lunco-luncosim --bin luncosim -j 4

For a live feature, launch only $LUNCOSIM_BIN with an explicit free API port, verify readiness, inspect ports and composed connections, and run the scene through the real executable. Report separately:

  • parsed and composed;
  • contract and topology accepted;
  • solver/runtime behavior observed;
  • visual behavior observed; and
  • warnings that remain, especially rejected force-loop diagnostics or solver warnings.

Keep long USD fixtures and asset-specific assertions in authored .usda and Rhai scene tests. Rust test stages should be minimal and programmatic, and only cover mechanisms that the production query/command surface cannot observe.

Never claim that a source parse or unit test proves the scene is physically correct.

© LunCoSim, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 1 other file (references) in skills/luncosim-architecture of LunCoSim/lunco-sim.

  • SKILL.md
  • references/usd-standard-map.md

Open the folder on GitHubat commit 43f1301

Compare with similar skills

Luncosim Architecture next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Luncosim Architecture compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Luncosim Architecture this skillLunCoSim/lunco-sim107—~19kAutomated safety check: PassApache-2.0
Update V8 Versionopeninterpreter/openinterpreter69k2 repos~845Automated safety check: PassApache-2.0
Firecrawl Page Scrape Integrationfirecrawl/firecrawl190k1 repos~944Automated safety check: PassISC
Migrate Core Code to Submodulestinyhumansai/openhuman42k—~2.6kAutomated safety check: PassGPL-3.0
Rust TDD Workflowrtk-ai/rtk83k—~753Automated safety check: NotesApache-2.0
OpenLogi macOS Permissions TriageAprilNEA/OpenLogi23k—~2.5kAutomated safety check: NotesApache-2.0

Similar skills

  • Update V8 Version

    openinterpreter/openinterpreter

    Bumps the pinned v8 and rusty_v8 versions in Codex, validates the release-candidate path with the v8-canary check, and traces failures to upstream build changes.

    69k GitHub starsUsed in 2 repos~845 tokens
    DevOps & CloudAuto-check passed
  • Adds Firecrawl's /scrape endpoint to application code to pull markdown, HTML, links, screenshots or structured data from a single known URL.

    190k GitHub starsUsed in 1 repo~944 tokens
    Data & AnalyticsAuto-check passed
  • Migrate Core Code to Submodules

    tinyhumansai/openhuman

    Plans and carries out moving non-host-specific code and its tests from the OpenHuman core into vendored tiny submodule libraries, then releases the submodule and re-pins the host.

    42k GitHub stars~2.6k tokensUpdated today
    DevelopmentAuto-check passed
  • Enforces red-green-refactor for Rust work, with idiomatic test patterns, a naming convention and a pre-commit gate of cargo fmt, clippy and test.

    83k GitHub stars~753 tokensUpdated yesterday
    Testing & QAAuto-check: notes
  • Decides whether an OpenLogi device problem on macOS is a privacy-permission (TCC) problem, using agent log lines, and says which identity needs which grant.

    23k GitHub stars~2.5k tokensUpdated today
    DevelopmentAuto-check: notes
  • Guide for writing idiomatic Rust code based on Apollo GraphQL's best practices handbook.

    5.6k GitHub starsUsed in 3 repos~1.1k tokens
    DevelopmentAuto-check passed

More from LunCoSim/lunco-sim

All 40 skills in this repo
  • Nightly Changelog

    LunCoSim/lunco-sim

    Generate concise LunCoSim nightly GitHub release notes with platform downloads, installation guidance, an AI-agent mission prompt, and a changelog link.

    107 GitHub stars~1.1k tokensUpdated today
    Auto-check passed
  • Build or repair a reusable scene component through a live LunCoSim Editor session.

    107 GitHub stars~4.4k tokensUpdated today
    Auto-check passed
  • Assembly Quality

    LunCoSim/lunco-sim

    Build or review a componentized LunCoSim USD assembly with a realistic, dimensionally checkable presentation.

    107 GitHub stars~1.6k tokensUpdated today
    Auto-check passed
  • Author Rhai Tests

    LunCoSim/lunco-sim

    Author and review LunCoSim behavioral, asset-backed, component, mission, visual, and requirements-verification tests.

    107 GitHub stars~3.5k tokensUpdated today
    Auto-check passed
  • Author Rhai Tool

    LunCoSim/lunco-sim

    Create, extend, register, or debug a reusable LunCoSim Rhai tool library for live USD authoring, component linting, inspection, or test support.

    107 GitHub stars~5.2k tokensUpdated today
    Auto-check passed
  • Author Tutorial

    LunCoSim/lunco-sim

    Author an interactive tutorial, guided lesson, onboarding flow, coach-mark tour, or objectives checklist in LunCoSim.

    107 GitHub stars~1.4k tokensUpdated today
    Auto-check passed

Works with

Questions about Luncosim Architecture

What does Luncosim Architecture do?

Review or build a LunCoSim feature that crosses USD, Modelica, Avian, Rust, or Rhai. Luncosim Architecture is an agent skill from LunCoSim/lunco-sim. Review or build a LunCoSim feature that crosses USD, Modelica, Avian, Rust, or Rhai.

How do I install Luncosim Architecture in Claude Code?

Run `npx skills add LunCoSim/lunco-sim --skill luncosim-architecture -a claude-code`. Or copy the skill folder (skills/luncosim-architecture in LunCoSim/lunco-sim) into .claude/skills/luncosim-architecture in your project. Claude Code loads it when a task matches its description.

How do I install Luncosim Architecture in Codex?

Run `npx skills add LunCoSim/lunco-sim --skill luncosim-architecture -a codex`. Or copy the skill folder (skills/luncosim-architecture in LunCoSim/lunco-sim) into .agents/skills/luncosim-architecture in your project. Codex loads it when a task matches its description.

Can I use Luncosim Architecture in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add LunCoSim/lunco-sim --skill luncosim-architecture -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/luncosim-architecture, .gemini/skills/luncosim-architecture, .github/skills/luncosim-architecture and .opencode/skills/luncosim-architecture in your project.

What does Luncosim Architecture need to run?

Going by SKILL.md and its folder, Luncosim Architecture needs the command-line tools its instructions call (kind, cargo and python3).

Does Luncosim Architecture access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Luncosim Architecture safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Luncosim Architecture use?

Luncosim Architecture is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Luncosim Architecture use?

About 19k tokens (SKILL.md is roughly 77k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 786 tokens, read only when the agent opens those files.

What are the alternatives to Luncosim Architecture?

Skills that share tags, products or a category with Luncosim Architecture: Update V8 Version (openinterpreter/openinterpreter, 69k stars), Firecrawl Page Scrape Integration (firecrawl/firecrawl, 190k stars), Migrate Core Code to Submodules (tinyhumansai/openhuman, 42k stars) and Rust TDD Workflow (rtk-ai/rtk, 83k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Luncosim Architecture?

LunCoSim (a GitHub organization) maintains it in LunCoSim/lunco-sim, which has 107 GitHub stars. The repository holds 40 skills in this directory. The repository was last updated on October 10, 2026.

Source: LunCoSim/lunco-sim on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.