Agent skill

Compose Multidomain Twin

by LunCoSim in LunCoSim/lunco-sim

Compose complete multi-domain LunCoSim Twins: USD structure and wiring, Modelica subsystem dynamics, cosimulation, and Rhai mission behavior.

Apache-2.0Auto-check passed

Install Compose Multidomain Twin

skills CLI
$ npx skills add LunCoSim/lunco-sim --skill compose-multidomain-twin -a claude-code

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

GitHub CLI
$ gh skill install LunCoSim/lunco-sim compose-multidomain-twin --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/compose-multidomain-twin .claude/skills/compose-multidomain-twin && 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
compose-multidomain-twin
GitHub stars
107
Token cost
~7.4k tokens
SKILL.md length
3,415 words
Files
1
Skills in repo
40
Repo updated
First seen
Licence
Apache-2.0

At a glance

Compose complete multi-domain LunCoSim Twins: USD structure and wiring, Modelica subsystem dynamics, cosimulation, and Rhai mission behavior.

  • Works in 5 steps: Identify the actual runtime checkout and… → Inventory assets/vessels/,… → Select the closest production exemplar:… → …
  • Building a lander
  • SKILL.md covers Start with a capability and…, Attach a program through the…, Use the generic assembly… and Componentized vehicle workflow, plus 7 more sections
  • Calls rg

What it does

Compose Multidomain Twin is an agent skill from LunCoSim/lunco-sim. Compose complete multi-domain LunCoSim Twins: USD structure and wiring, Modelica subsystem dynamics, cosimulation, and Rhai mission behavior. Use when building a lander, rover, spacecraft, or fleet; wiring a Modelica model to a USD body; connecting GNC, propulsion, power, or thermal domains; or packaging a scenario as a Twin. Before designing, audit existing capabilities and the closest production exemplar. The skill covers Twin manifests, one program prim per domain, generated network roots, native USD…

Its SKILL.md is about 7.4k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

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

When your agent uses it

  • Building a lander
  • Wiring a Modelica model to a USD body
  • Thermal domains
  • Packaging a scenario as a Twin

Example prompts

  • “/compose-multidomain-twin”

Requirements

  • Python 3

Workflow steps

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

  1. Identify the actual runtime checkout and the mission/research repository. Do
  2. Inventory assets/vessels/, assets/components/, assets/models/,
  3. Select the closest production exemplar: read its reusable asset, its
  4. Write a small private matrix before editing
  5. Classify the work as an engine substrate, reusable asset, mission assembly,

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:

    • rg

    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

Compose Multidomain Twin loads about 7.4k tokens when it runs. Until then it costs about 217 tokens; SKILL.md has 3,415 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~217
When it runs · the whole SKILL.md, loaded when a task matches
~7.4k

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). 3,415 words, ~7,354 tokens.

Download SKILL.mdSave it as .claude/skills/compose-multidomain-twin/SKILL.md (or your agent's skills folder).
name
compose-multidomain-twin
description
Compose complete multi-domain LunCoSim Twins: USD structure and wiring, Modelica subsystem dynamics, cosimulation, and Rhai mission behavior. Use when building a lander, rover, spacecraft, or fleet; wiring a Modelica model to a USD body; connecting GNC, propulsion, power, or thermal domains; or packaging a scenario as a Twin. Before designing, audit existing capabilities and the closest production exemplar. The skill covers Twin manifests, one program prim per domain, generated network roots, native USD connections, port wiring, scenario orchestration, Twin-owned SysML requirements/verification, and the rule that vehicles are authored USD assets, not Rust structs. It also covers PortRegistry ownership and environment-owned gravity. See docs/architecture/33-spacecraft-modeling.md and docs/architecture/34-scenario-and-multidomain.md.

Composing a multi-domain Twin

A full mission layers cleanly — never blur the layers:

Layer ("…")OwnsLives in
Structure + wiring ("what")bodies, colliders, mass/inertia, joints, topology, program prims, port connectionsUSD (authored)
System intent + acceptance contractparts/ports/connections, requirements, satisfy/verify traceabilitySysML v2 (enabled by default in the production app/core/server)
Subsystem dynamics ("how a part behaves")thrust, propellant, battery, thermal, controllersModelica / rhai (cosim)
Substrate + behavior library ("the laws")solver, force/joint/port plumbing, parameterized wheel/suspension/frictionRust (reusable, never bespoke)

Rust ships parameterized behaviors; it never hardcodes a vehicle. A 6-wheel rover is a USD file, not a Rust struct — the physics/materials philosophy applied to whole vehicles.

Who computes what. Rust owns rigid-body kinematics and dynamics — bodies, colliders, contacts, joints. Modelica owns everything else that evolves: thermal, electrical, propulsion, structural. Modelica reaches physics through cosim ports, and may also carry GNC or flight-software math (an equation is an equation); what it must never become is a second physics engine. SysML owns requirement intent and traceability; Rhai observes the composed stage and emits the executable verdict. Rhai is the default scenario backend. Python is optional and is not part of the normal Twin workflow.

For mission-level work, apply the generic mission and engineering quality gates before composing domains. Establish the ConOps and operating modes, allocate requirements and interfaces down the product tree, choose fidelity by intended use, and define nominal, off-nominal, and recovery scenarios. Verify detached components before integration, validate the integrated Twin against ConOps, and baseline the exact sources, clocks, solver settings, and seeds used for each evidence run.

Start with a capability and exemplar audit

Before authoring a new vehicle, domain, controller, or Rust mechanism, inspect the target checkout and the repositories that supplied the request. Separate what is already implemented from what is merely not yet composed for this mission.

  1. Identify the actual runtime checkout and the mission/research repository. Do not transfer a gap from one repository to the other without checking the target checkout.

  2. Inventory assets/vessels/, assets/components/, assets/models/, assets/scenes/, and assets/scenarios/ with rg --files.

  3. Select the closest production exemplar: read its reusable asset, its composing scene, and its authored test. A tutorial or demo may explain an idea but is not automatically the production contract.

  4. Write a small private matrix before editing:

    QuestionAnswer
    Generic mechanism already exists?yes/no + source path
    Closest reusable exemplar?asset + scene + test
    Mission-specific authoring missing?USD/Modelica/Rhai/data
    External dependency required?yes/no + explicit scope
    Runtime evidence available?parse/compile/readiness/behavior/visual
  5. Classify the work as an engine substrate, reusable asset, mission assembly, external dependency, or evidence gap. Only an engine-substrate result justifies a Rust change. If a generic mechanism and exemplar already exist, compose and parameterize them first.

This audit is a decision aid, not a new persistent runtime registry. Keep the final ownership and parameters in the existing USD, Modelica, Rhai, and Twin contracts.

Check Twin-wide names before loading

Run the explicit namespace pre-flight after adding a source root, tool library, shader, or referenced USD asset:

rhai
query("ValidateTwin", #{path: "/work/rover-twin", policy: "error"});

Retain the returned operation_id and poll ValidateTwin with only that ID. Read ready.report; failed.diagnostic is terminal. Mounted browser callers use the current assigned twin://<authority> instead of a native folder. See validate-assets.

The inspector reports only collisions in the same resolver scope and includes every owner/source so the repair is made in the authored Twin. RunLint can run the same check against the active Twin with cmd("RunLint", #{scope: "twin", policy: "warn"}) on native or a mounted browser Twin. Retain its queued lint revision and inspect GetDiagnostics for the actual ready/failed scope. Source failures fail that scope regardless of collision policy, and retirement cancels its exact preparation. Keep resolver policy in assets/scripting/policy/lint_twin.rhai; do not add a Rust-side rename, silent precedence rule, or duplicate namespace registry.

Physics ports vs sensors — pick the wrong one and you author a bug
Physics portsSensors
There becausethe body/collider EXISTSyou AUTHORED an instrument in USD
Portsposition_*, velocity_*, contact, contact_forcerange, accel_*, spec_force_*, contact
Addsnothing — ground truthmount offset, range limits, out-of-range mode, noise, failure
Wire tophysical parts — struts, dampers, structureflight software — GNC, OBC, autopilot

A physical part reads PHYSICS: a strut's glow takes the force port off its own prismatic joint, because a leg carries load when the ground pushes on it — not when an instrument says so. Flight software reads SENSORS, because a computer knows only what its instruments report: DescentGuidance reads the altimeter with its mount offset and rangeMax, not the true height.

Backwards costs real bugs. An altimeter's datum sits above the pads, so gating a strut on it forces a hand-copied constant to restate that offset, and the legs light before touchdown. When a constant in a .mo exists only to translate between two prims' positions, the wire is wrong.

A sprung mechanism belongs to the SOLVER, not to a domain model. A landing leg's passive shock absorber is a PhysicsPrismaticJoint with standard PhysicsDriveAPI:linear coefficients and the narrow PhysicsDriveAPI:linear coefficients. The native Avian prismatic joint is the sole axial mechanism in the existing substep solver, and its stroke and reaction are the joint's own displacement and output-only force ports. Do not write a .mo for the spring and do not animate it from Rhai: Modelica earns its keep where a domain equation has no solver already integrating it.

Sign is a property of the joint. force = stiffness * (targetPosition - displacement) makes the reaction opposite in sign to the displacement, so a compressed strut reads NEGATIVE displacement and POSITIVE force, and the axis points the way the strut extends. Author it once in physics:localRot0; never correct it downstream in a wire's lunco:factor:, which is a unit conversion.

A sensor reads physics; it never re-derives it. The touchdown switch and the collider contact ports share one computation (avian::contact_of). Two copies are free to drift, and nothing in the log says which one you are looking at.

No per-tick computation. Prefer an on-demand port read over a mirror component kept in step by a sync system, and Changed<T> over an unfiltered system. Never per-tick work in rhai — except in a rhai test, where stepping is the point.

Attach a program through the shared contract

Use AttachProgram { doc_id, spec } for a new Modelica participant. Python is an optional python-feature integration and should be used only when the Twin explicitly requires it; it is not the normal scenario backend. For Twin-owned requirements and verification cases, use sysml-requirements rather than adding requirement metadata to a program prim. The command validates the source path and explicit scalar interface, then authors the LunCoProgramAPI child, defaults, and native USD connections as one journal/undo change set. The Models palette and the assembly_edit Rhai tool call this same command; do not add a marker component or a source-specific Rust path.

rhai
assembly_edit::attach_program(doc, #{
    edit_target: "@root@",
    host_path: "/Lander",
    name: "Guidance",
    source_asset: "lunco://models/DescentGuidance.mo",
    inputs: [assembly_edit::program_input_connection("altitude", "/Lander.outputs:position_y")],
    outputs: [assembly_edit::program_output("force_y", ["/Lander.inputs:force_y"])],
    realtime_safe: true,
});

Every input has one authored default or one USD connection. Every output names its consuming USD input properties. An empty contract is source-only and must be completed before ListPorts, CosimStatus, or force exchange can prove a running participant. Use @root@ for persistent scene content and @runtime@ only when a live overlay is the explicit intent.

This skill is the assembly layer over the single-domain skills: build-usd-scene (author the scene), authoring-vessel-controllers (a vessel's GNC), author-scenario (behaviour), run-modelica (the .mo models), inspect-simulation (verify the chain). For reusable builders, component lints, and live typed-operation helpers, use author-rhai-tool. For architecture decisions and standard-schema checks, read luncosim-architecture. It is the gate against special-case Rust, duplicate USD vocabulary, and compatibility paths.

Use the generic assembly checkpoint

For a new rover, lander, or multi-domain scene, use the shared Rhai model_authoring facade before composing domain-specific policy:

  1. model_context(doc, root, edit_target) reads the complete composed tree and preserves its generation.
  2. readiness_report(doc, root, edit_target, policy) combines the explicit topology, physicality, mount, connection, control, and runtime checks.
  3. scene_recipe returns dry references, terrain, camera, initial-state, route, and program records.
  4. port_graph and wiring_plan discover and validate standard USD inputs:/outputs:/connectors: endpoints before typed SetConnection ops are reviewed.
  5. publish_component validates a reusable component and returns an explicit Save-As plan.

Keep vehicle manifests, limits, and mission thresholds in the Twin's Rhai package. The facades do not create a Modelica compiler path, a vehicle schema, or a Rust builder. See the scripting guide for signatures and apply boundaries.

Componentized vehicle workflow

Build a vehicle as a small set of contracts, not one monolithic USD file. Put each independently reusable, articulated, or domain-owning subsystem in its own component boundary when it has a local frame, mount interface, mass or collision owner, parameters, or a useful independent test. Typical boundaries are an egress mechanism, landing leg, solar array, avionics package, payload adapter, and propulsion/attitude network. Keep one-off visual trim in the assembly when splitting it would add no reusable contract.

For each component, define its topology, frame/datum, known requirements and limits, and Rhai requirement test before composing it. Use the live headful Editor to author the component with typed USD operations, inspect and screenshot it, and run its boundary tests in the same runtime session. Then assemble through explicit references, host joints, collections, and native connections, and run a second integration suite for counts, placement, clearance, reference targets, joint endpoints, and cross-domain wires. A component pass is not an assembly pass.

For a repeated parametric component, use the dynamic assembly_builder::component_bundle_facts/component_bundle_plan pair to keep visual geometry, collision ownership, mass/inertia, dimensions, mount frames, and actuator endpoints in one validated contract. Keep deployment policy and mission values in Twin-owned Rhai/USD; the bundle does not create a Modelica participant, guess a physics joint, or add a vehicle-specific Rust schema. Add explicit typed body/joint or socket plans at the assembly boundary, then verify both the component contract and the composed Twin.

Keep the detail level at contract fidelity: standard USD primitives are enough for silhouettes, mounting envelopes, colliders, and tested interfaces. Add meshes or parametric detail only when it changes a requirement or the visible identity. Never flatten a referenced component into the assembly merely to avoid authoring its own contract. If a typed USD gap prevents creating a variant set, replacing a reference list, or authoring required metadata, report the Rust/typed-editor gap instead of using duplicate hidden geometry.

The Twin (on-disk mission unit)

A Twin = a folder + a twin.toml manifest that owns a default USD scene:

toml
name = "luncosim"
version = "0.1.0"
description = "…"
[usd]
default_scene = "sandbox_scene.usda"   # loaded as the active stage on open; other .usda here are a referenceable library

Everything the mission needs lives under that folder: the scene, referenced vehicle assets, .mo models, .rhai scenarios. Open the Twin and the default scene loads.

Reuse the closest working composition

Copy the composition shape through USD references; do not copy a whole example into a new bespoke implementation. Preserve the exemplar's ownership, network roots, input/output contract, lifecycle, and test pattern. Override geometry, parameters, initial conditions, and mission policy in the new asset or scene.

For Modelica, first compose existing component classes through USD and the generated network wrapper. A new vehicle-level .mo is justified only when the required equations or public interface are absent. Do not create one file per mission domain merely to rename existing LunCo.* classes.

Decision 1 — a multi-domain vehicle is one program prim per domain + connections (SSP)

A program is a PRIM, not an attribute on the thing it drives — the same reason a UsdShade shader is a prim: it has typed ports, and ports connect. Model each physical domain as its own a Scope applying LunCoProgramAPI under the vehicle Xform, each naming its own .mo, wired through the port surface. This is FMI/SSP — no new machinery.

usda
def Xform "Lander" (PhysicsRigidBodyAPI …, CollectionAPI:components) # body + network root
{
    float inputs:force_local_y.connect = </Lander/GNC.outputs:thrust>   # GNC thrust → body force

    def Xform "Battery" (
        prepend references = @lunco://components/power/battery.usda@</Battery>
    ) {}
    uniform token collection:components:expansionRule = "explicitOnly"
    prepend rel collection:components:includes = [</Lander/Battery>]

    def Scope "GNC" (prepend apiSchemas = ["LunCoProgramAPI"]) {
        uniform asset info:sourceAsset = @lunco://models/DescentGuidance.mo@
        uniform bool  lunco:program:realtimeSafe = true                 # it drives a force
        float inputs:altitude.connect     = </Lander.outputs:position_y>
        float inputs:descent_rate.connect = </Lander.outputs:velocity_y>
        # Consume the battery output at its authored USD path.
        float inputs:engine_enable.connect = </Lander/Battery.outputs:soc_out>
        float inputs:g = 1.62                                           # only if the Modelica contract declares a runtime input
    }
    def Scope "Therm" (prepend apiSchemas = ["LunCoProgramAPI"]) {
        uniform asset info:sourceAsset = @lunco://models/LunCo/Thermal/ThermalMass.mo@
    }
}

The electrical network is the explicit CollectionAPI:components collection on the assembly root. It does not need a domain-named child or an inputs:load wire. Battery.mo exposes a Pin, and a pin carries a flow variable — the current is the circuit's answer, not a number anyone writes in. Wire a physical bus by connect()-ing pins inside one Modelica model; only signals (a throttle, a setpoint, a temperature reading) cross as USD port connections. See 54 — The Electrical Domain.

A wire is a native USD connection, authored on the prim that CONSUMES the value. Do not host N solvers on one entity — that forces SimComponent to a multi-instance map and touches the propagation core. One program prim per domain gives the same composition with zero core change and clean per-domain telemetry.

Show full SKILL.md (1,407 more words)Show less
Acausal networks and solver boundaries

There are two different meanings of multidomain:

text
acausal components inside one generated Modelica root       supported
acausal edges between separate runtime solver participants  require fusion

One CollectionAPI:components root is one generated Modelica participant and one public boundary. The synthesizer may emit several connected units below that root; those units are Modelica structure, not extra ECS entities. Keep Pin, HeatPort, FluidPort, Flange, and other conservation connectors inside the root that solves them.

Across separate roots, use typed scalar causal USD connections. A one-step or macro-step boundary is an explicit coupling choice, not an acausal connect(). If zero-delay bidirectional feedback is physically required, synthesize the coupled components into one Modelica root and solve one DAE. Do not fake a cross-root algebraic connection with a Rhai loop, a duplicated state, or a hidden fallback. Automatic island fusion is a future capability, not a reason to redesign a working MVP.

A vessel's OWN flight-control system is the one exception to the child prim: it is not separable from the airframe (its inputs: are what the stick talks to), so the vessel prim applies LunCoProgramAPI in place — see authoring-vessel-controllers.

Decision 2 — the PortRegistry is the ONE input-write path

SetModelInput uses the shared PortRegistry for a declared live input port; SetPorts, Rhai set(id,name,v), Python, and wires use the same port owner. External live-session SetModelInput, external API/direct/non-Simulation Rhai SetPorts, and live port-inspector writes to stable scene targets enter the bounded session queue and reach that port at their admitted fixed tick. External SetPorts callers supply a stable producer id; Twin Rhai uses its actor identity. Local Modelica canvas and port-inspector inputs use the local session id. Simulation-clock Rhai writes remain derived behavior. Co-simulation retains Twin/Simulation Rhai setpoints separately from controller writes: controller holds take precedence while active, and authored program setpoints remain underneath them. External ReleasePort and ReleaseControl commands use the same stable producer and fixed-tick admission contract. ReleaseControl clears local setpoint holds without writing replacement values. ControlAuthorityChanged queues the internal ReleaseControlInputs lifecycle event, which clears controller-owned holds and held semantic intents at its ordered fixed-tick commit while preserving authored program setpoints and physics state. A disconnected controller uses the same lifecycle event. Twin policy owns any safe setpoint and must write its named input values explicitly through SetPorts. A bare editor-only model without a live port keeps its ModelicaModel.inputs owner. On a generated Modelica root that also carries InputPorts, InputPorts is the authored public command boundary and therefore wins for names it declares; the generic bridge mirrors those accepted values into ModelicaModel.inputs. Other propagated model inputs remain on SimComponent.inputs. A direct ModelicaModel.inputs write is clobbered within one frame. Simulation-clock Rhai writes remain derived from deterministic scenario evaluation and are applied by that evaluation pass.

Use SimulateIntent for a held semantic control and SimulateIntentEdge/Rhai intent_pulse for a discrete transition. The latter publishes one target-scoped intent.edge event; Rhai owns the action policy and Modelica owns continuous state, so the edge must not be translated into a vehicle-specific Rust write or emulated with two ordered held commands. API and direct typed callers provide a stable nonzero producer_id; actorless Rhai callers provide one too. Twin Rhai uses its route and stable actor id, so leave producer_id out of scenario commands. Releasing one producer does not release another. This is live control state, not a durable session replay.

For an operator-facing causal check, keep the edge result id and call query("CausalTrace", #{target: target, correlation_id: edge.id}). The read-only response composes the existing ControlBinding, PortRegistry, USD connection/native-joint admission, and SignalRegistry facts. It reports missing/pending stages explicitly and never becomes a second control or telemetry route. Externally admitted discrete edges also report the committed scene generation, effective tick, and per-tick sequence; deterministic Rhai simulation edges carry no external admission stamp.

Battery empty events use the authored 0.1% usable-storage reserve in Battery.mo; do not replace that physical boundary with Rust actuator policy or a solver-epsilon comparison.

Decision 3 — the scenario is a first-class USD concept

A scenario is the scene that bundles vehicles (referenced assets) + cosim wiring

  • per-vehicle behaviour + one orchestration script:
usda
def Scope "Scenario" ( kind = "component" )
{
    custom string lunco:scenario = "rover-surface-ops"

    def Scope "Mission" (prepend apiSchemas = ["LunCoProgramAPI"]) {
        uniform asset info:sourceAsset = @scenarios/rover_surface_ops.rhai@
        # Keep production mission logic in the referenced source asset.
    }
}
  • Orchestration (rhai): phases via the sequencer (descend → touchdown → deploy → handover → task_1…), advancing on port-read predicates (altitude, joint presence, SoC, distance, temp).
  • Per-vehicle scripts: a a Scope applying LunCoProgramAPI child prim on the vehicle for local behaviour (flight assist, autonomy helpers) — delete the prim and the behaviour goes with it.
  • Objectives / scoring: rhai predicates over ports — no new engine.

Recipe

  1. Audit: inventory the target checkout, choose the closest production exemplar, and classify existing capability versus mission authoring.
  2. Twin: create/pick a folder with twin.toml ([usd] default_scene).
  3. Reference vehicles: pull authored assets into the scene (e.g. assets/vessels/rovers/{skid,ackermann}_rover.usda, a lander) — wheel count, params, joints, drive type all come from USD; nothing hardcoded.
  4. Add subsystems per vehicle: apply LunCoProgramAPI to each standalone program prim and apply CollectionAPI:components to each assembled network root. The body carries PhysicsRigidBodyAPI plus the force connections. Reuse existing component classes and generated-network patterns before adding a new .mo source.
  5. Wire cross-domain ports with connections on the consumer (inputs:load.connect = </Lander/GNC.outputs:thrust>, inputs:engine_enable.connect = </Lander/Battery.outputs:soc_out>, …).
  6. Add the Scenario prim: a LunCoProgramAPI child naming the orchestration script (phases + objectives as port predicates), plus a LunCoProgramAPI child on each vehicle for its own behaviour.
  7. Open + verify: load the Twin, then use inspect-simulation — cosim_status to see the whole Modelica→physics chain, read_ports for specific values, a screenshot to confirm motion. Iterate on the .usda/.mo/.rhai (all hot-editable).

Build vertically and keep the last passing stage:

text
static Twin/readiness
  -> rigid body/contact
  -> one closed control/actuation loop
  -> additional physical networks
  -> deployment/mobility
  -> mission policy and evidence

Do not add terrain, several new domains, and a long scenario to an unproven vehicle at once. At every stage record whether the result is only parsed, compiled, ready, numerically observed, or visually accepted.

Gotchas

  • Don't apply gravity in .mo — lunco-environment applies it separately; doing both double-counts.
  • Don't write ModelicaModel.inputs directly — co-simulation sync owns those values each tick. SetModelInput uses the shared port-first input path.
  • set_input(me,…) is not a rhai verb — inside a scenario use set(me, "port", v) (routes through write_port).
  • A vehicle is a USD file — spawn/param it in USD; if you're writing a Rust struct for a specific rover, stop.
  • Unwired algebraic Modelica inputs fold to their default — see authoring-vessel-controllers for the der-feed / wiring fix.
  • Per-domain identity is the point — one LunCoProgramAPI prim per subsystem gives clean per-domain telemetry; don't collapse them onto the body.
  • Do not infer a simulator gap from a mission repository — check the target asset library, source, and production test first.
  • Do not fork a tutorial model as a production controller — reuse the nearest shipped controller and composition scene, then override authored facts.
  • Do not create a monolithic domain model by default — compose reusable Modelica classes through USD and let the generated wrapper own the network.
  • Acausal is not the same as cross-participant — use connect() inside one solving root and typed causal signals between roots.
  • Do not add a Rhai sleep after wiring a generated domain island. USD composition defers its boundary edges until the island's ModelicaModel publishes the runtime contract. Use the readiness query/policy to wait for that stage; a compiler-pending endpoint is assembly progress, while an unknown port after the contract is terminal is a real authoring failure.
Declared topology is not a sample

Keep the set of connectable output names separate from the values sampled this tick. DeclaredOutputPorts records topology; SimComponent.outputs records current samples. An empty codeless LunCoEnvironmentProbeAPI asset is valid: the projection supplies the authoritative environment names, while the environment domain removes absent samples instead of fabricating zeroes or retaining stale values. Do not add dummy USD properties or an AntennaTracker alias to hide a missing declaration. See tutorial-autopilot-and-port-contracts.

For a generated Modelica domain, the complete parsed wrapper output interface—including topology-derived member aliases selected by the authored synthesizer—is published before USD telemetry declarations are projected. Keep this lifecycle ordering intact: a compile-pending wrapper is assembly state, not a missing-port fallback, and live values still come only from SimComponent.outputs.

Handoff report

When passing a composed Twin to another agent, record the target checkout and revision, reused exemplar paths, authored files, network roots and boundaries, assumption status, exact checks, runtime/API evidence, visual evidence, and the next blocker. State explicitly which claims are source inspection only. Include the active branch/worktree, preserved dirty changes, production binary and API port, open document/preview ids, registered tool libraries, exact public sources, component-to-assembly ownership, the last successful generation, and the next safe command. Separate preflight, typed projection, runtime, and visual evidence. Keep the handoff factual and scoped; do not promote mission-specific assumptions into general skill rules. The handover is not complete until a new agent can resume without guessing which document, layer, tool scope, or test runner is authoritative.

© 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

Just SKILL.md in skills/compose-multidomain-twin of LunCoSim/lunco-sim.

Open the folder on GitHubat commit 43f1301

Compare with similar skills

Compose Multidomain Twin 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.

Compose Multidomain Twin compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Compose Multidomain Twin this skillLunCoSim/lunco-sim107—~7.4kAutomated 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 today
    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 Compose Multidomain Twin

What does Compose Multidomain Twin do?

Compose complete multi-domain LunCoSim Twins: USD structure and wiring, Modelica subsystem dynamics, cosimulation, and Rhai mission behavior. Compose Multidomain Twin is an agent skill from LunCoSim/lunco-sim. Compose complete multi-domain LunCoSim Twins: USD structure and wiring, Modelica subsystem dynamics, cosimulation, and Rhai mission behavior.

When should I use Compose Multidomain Twin?

Compose Multidomain Twin fits situations like: building a lander; wiring a Modelica model to a USD body; thermal domains; packaging a scenario as a Twin.

How do I install Compose Multidomain Twin in Claude Code?

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

How do I install Compose Multidomain Twin in Codex?

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

Can I use Compose Multidomain Twin 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 compose-multidomain-twin -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/compose-multidomain-twin, .gemini/skills/compose-multidomain-twin, .github/skills/compose-multidomain-twin and .opencode/skills/compose-multidomain-twin in your project.

What does Compose Multidomain Twin need to run?

Going by SKILL.md and its folder, Compose Multidomain Twin needs the command-line tools its instructions call (rg). Our summary lists: Python 3.

Does Compose Multidomain Twin 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 Compose Multidomain Twin 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 Compose Multidomain Twin use?

Compose Multidomain Twin 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 Compose Multidomain Twin use?

About 7.4k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Compose Multidomain Twin?

Skills that share tags, products or a category with Compose Multidomain Twin: 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 Compose Multidomain Twin?

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.