Install
$ agentstack add skill-leventilo-mobius-viz-mapper ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
viz-mapper V2 (V8 monstrueux)
1. Purpose
Translate verified simspec.verified.json + physics.json + claims.json + figures.json into a typed viz_spec.json that conforms exactly to the V8 viz_spec.schema.json. Server-side ajv validation enforces shape post-receive.
This skill does NOT:
- author HTML, CSS, or JavaScript
- decide the archetype (it copies
simspec.viz.scene_archetype) - run code_execution (the tool is attached only because Skills v1 require it)
- compute physics (primitives in Phase E own that)
- write narration (the
narrationskill owns that)
The downstream renderer (public/runtime/render.js) auto-projects any V7 spec into V8 shape via projectV7ToV8, dispatches each experiment.panels[] entry to a panel renderer, evaluates experiment.derived[] as a DAG on every slider change, and updates live readouts[] and dynamic annotations.
2. Output format (mandatory)
Emit exactly one JSON object inside a single fenced ``json ... `` block at the END of the reply. No prose before or after. No tool_use.
Top-level shape:
{
"paper": { "title": "...", "arxivId": "...", "authors": ["..."] },
"experiments": [ { /* one or more experiment objects */ } ]
}
For a single-phenomenon paper emit experiments length 1. For a paper with multiple distinct setups emit experiments length N — each independent. Most papers emit ONE experiment containing MULTIPLE panels via experiment.panels[].
3. Inputs
Read from the container filesystem:
/mnt/session/runs/{runId}/simspec.verified.json— canonical input/mnt/session/runs/{runId}/physics.json— equations, regime/mnt/session/runs/{runId}/claims.json— numerical claims/mnt/session/runs/{runId}/figures.json— figure metadata
4. V8 monstrueux: structural decisions
4.1 panels[]: multi-region scenes
V7 forced one canvas per experiment via the archetype field. V8 introduces panels[] so a single experiment can contain N rendering regions sharing the same sliders, derived state, readouts, and layer toggles. Examples:
- Young's double-slit: 1 panel (field_2d) with 3 traces (envelope, intensity, measured).
- Lorenz attractor: 4 panels in a 3×2 grid — 3 stacked timeseries (X(t), Y(t), Z(t) in col:1 rows 1..3) plus 1 scatter return-map (col:2, row:1, rowspan:3).
- Schrödinger tunneling: 1 panel (field_2d) with 1 fill-style trace + dynamic V₀ annotation.
Choose layout: {kind: "single"} when N=1; layout: {kind: "grid", rows, cols} for N>1. Maximum 4 panels for timeseries, 2 for field2d, 1 for raycanvas / orbital3d / heatmap_2d.
4.2 derived[]: compute DAG
V7's equations[].compute_js is one expression for display. V8 introduces derived[] as a topologically-sorted DAG of intermediate quantities. Each derived has:
id: matches paper variable name (e.g.visibility_V,intensity_I)expr: JS expression in the compute_analytic sandbox. Bare names (sin,cos,PI) — neverMath.sin. Forkind: "array"the expr returns a length-N array, typically via(function(){ return x.map(xi => ...); })(). The implicitxis a Float32Array of lengthdefault_grid_non[0,1].deps: list of slider ids and other derived ids referenced. Used for topo sort.kind:"scalar"(one number) or"array"(length-N Float32Array).
Authoring rule: build the DAG leaf-to-root. Sliders are leaves. Final array nodes are rendered by traces.
4.3 readouts[]: live numeric strip
A readout is one row in a strip of live values shown under (or above) the canvas. Each binds to a dynamic_value referencing a kind:"scalar" derived id, with optional scale (multiplier for unit conversion), format, and unit.
Example: { id: "ro-dx", label: "δx =", dynamic_value: { compute_id: "fringe_sep", scale: 1000, format: "fixed2", unit: " mm" }, position: "under_canvas" }.
4.4 dynamic annotations
When an annotation's text contains a number that changes with sliders, use text_template with a {value} placeholder + dynamic_value. The renderer substitutes {value} on every recompute.
Example: { ..., text_template: "δx = {value} mm", dynamic_value: { compute_id: "fringe_sep", scale: 1000, format: "fixed2" } }.
Static annotations keep using plain text.
4.5 layer_toggles[]: trace-id-driven chip strip
V7 layer_toggles was a string keyword list (["rays", "grid"]). V8 promotes it to objects: { id, label, trace_ids[], default_visible }. trace_ids lists the panel.traces[] ids whose visibility is bound to the chip; the renderer hides traces in real time via a click handler. V7 string form still works (auto-projected).
5. Field-by-field authoring rules
5.1 paper
Copy from simspec.paper.
5.2 experiment.id, label
id: kebab/snake matching ^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$. label: human-readable (≤96 chars).
5.3 archetype (still required)
Copy simspec.viz.scene_archetype verbatim. The archetype acts as a thin orchestrator dispatching to panels[]. Use it as a hint to the renderer, but the actual visual is decided by panel.kind values.
5.4 evolutionkind, statesource, swept_param
evolution_kind←simspec.simulation.evolution_kind(static | time_series | parameter_sweep).state_source:framesfor primitive-driven series;analyticfor closed-form.swept_param: required iffparameter_sweep. Either a slider id OR a derived id (V8).
5.5 canvas
Defaults: { width: 760, height: 480, aspect: "16/9", dpr_max: 2 }. Use 1024×600 for grid layouts.
5.6 palette_map
Map role → c0..c5. Lowercase + spaces + dashes + underscores + digits, 1-64 chars, must start with a letter. Every role referenced by sliders/annotations/equations/primitivebindings/traces/readouts/layertoggles MUST be a key in palette_map.
5.7 sliders[]
Walk simspec.viz.sliders[]. Copy id/label/bindsto/unitucum, set min/max from range, step = (max-min)/100. Pick formatter from {fixed1, fixed2, fixed3, scientific, integer, percent, degrees, none}. R-INLINE: every slider must be a real interactive control, no hardcoded constants.
5.8 defaultgridn
512 default; 1024 for fine-detail like Young fringes; 256 for Schrödinger frames. Bounded [32, 4096].
5.9 derived[]
Authoring protocol:
- Start from slider ids as leaves. Add scalar derived for closed-form quantities (e.g.
fringe_sep = lam * L / d,visibility_V = (b/lam) * sin(...) ...). - Build array derived for plotted quantities. The implicit
xis on [0,1]; encode the physical domain into the expr (e.g. for Young's screen ±12.5 mm:var xi_m = (x - 0.5) * 0.025; ...). Pattern:(function(){ return x.map(function(xi){ var x_mm = (xi - 0.5) * 25; return cos(2*PI*d*x_mm/(lam*L*1000))**2; }); })(). - List every slider id and every other derived id referenced in
deps[]. - Final node consumed by a trace must be
kind: "array". - Sandbox tokens: bare math names (
sin,cos,PI,sqrt,abs,exp,pow,min,max,floor, etc.). Forbidden:window,eval,Function,import,require,this,__proto__,constructor,prototype,new, backticks,${}interpolation. Thefunctionkeyword +=>are allowed for the array idiom.
5.10 readouts[]
One readout per claim from the paper that depends on sliders. Examples for Young: δx (fringe spacing), V (visibility). Bind each to a scalar derived id via dynamic_value. Use scale for unit conversion (e.g. scale: 1000 to display metres as mm).
5.11 panels[]
For each visual region in the paper's key figure(s), emit one panel:
{
"id": "panel-main",
"kind": "field_2d",
"label": "Far-field intensity",
"position": { "row": 1, "col": 1 },
"axes": { "x_label": "screen x", "x_unit": "mm", "y_range": { "min": 0, "max": 1 } },
"extras": { "show_legend": true, "show_gridlines": false },
"traces": [
{ "id": "tr-env", "label": "envelope", "source": "analytic", "compute_id": "envelope_E",
"palette_role": "fringe maxima", "style": "line", "stroke_width_px": 1, "stroke_dash_pattern": [4, 4], "z_order": 0 },
{ "id": "tr-int", "label": "intensity", "source": "analytic", "compute_id": "intensity_I",
"palette_role": "interference field", "style": "line", "stroke_width_px": 1.6, "z_order": 1 }
]
}
panel.kind enum: field_2d, time_series, scatter, heatmap_2d, ray_canvas, orbital_3d, particle_cloud, lattice, schematic. Choose by visual character of the figure.
5.12 panel.traces[]
source: "analytic"+compute_idreferencing akind: "array"derived.source: "frames"+primitive_id+state_keyreferencing simspec.primitives[].state.palette_role→ palette_map.style:line(default),fill(Schrödinger ψ²),dots,bars,area.shadow_blur_px+shadow_color_rolefor emphasis (Young measured-detector trace).stroke_dash_pattern: [4, 4]for envelope/reference curves.z_orderfor stable layering.
5.13 annotations[]
Use text_template + dynamic_value whenever the displayed number depends on sliders. Use plain text for static labels. Anchor space normalised [0,1] for archetype-agnostic placement.
5.14 equations[]
Up to 8 governing equations. Optional compute_js (legacy V7) is supported but in V8 prefer authoring derived[] and using equations[] purely for KaTeX display. New size: "small" | "medium" | "large" controls KaTeX rendering size.
5.15 interactions
{
"play_button": (evolution_kind != "static"),
"scroll_lock": false,
"drag_camera": (archetype ∈ {orbital-3d, glass-dispersion, particle-cloud, lattice}),
"reset_button": (evolution_kind != "static"),
"layer_toggles": [ /* layer_toggle_spec[] */ ]
}
6. Worked example 1 — Young's double-slit (1 panel, 3 traces, 4 derived, 2 readouts, 1 dynamic annotation)
{
"paper": { "title": "Young's double-slit interference with single photons", "arxivId": "2401.02351" },
"experiments": [
{
"id": "young",
"label": "Two-slit interference",
"archetype": "field-2d",
"evolution_kind": "static",
"state_source": "analytic",
"default_grid_n": 1024,
"caption": "δx = λL/d sets the fringe spacing; sinc² envelope from finite slit width.",
"canvas": { "width": 760, "height": 480, "aspect": "16/9", "dpr_max": 2 },
"palette_map": {
"single-photon source": "c0",
"double-slit aperture": "c1",
"interference field": "c3",
"envelope curve": "c4",
"scanning detector": "c5"
},
"layout": { "kind": "single" },
"sliders": [
{ "id": "lam", "kind": "physics", "label": "wavelength λ", "binds_to": "primitives[id=monochromatic-source].parameters.wavelength.value",
"min": 4e-7, "max": 1e-6, "default": 8.1e-7, "step": 1e-8, "unit_ucum": "m", "formatter": "scientific", "palette_role": "single-photon source" },
{ "id": "d", "kind": "physics", "label": "slit separation d", "binds_to": "primitives[id=double-slit-aperture].parameters.slit_separation.value",
"min": 1e-4, "max": 2e-3, "default": 6.2e-4, "step": 1e-5, "unit_ucum": "m", "formatter": "scientific", "palette_role": "double-slit aperture" },
{ "id": "b", "kind": "physics", "label": "slit width b", "binds_to": "primitives[id=double-slit-aperture].parameters.slit_width.value",
"min": 5e-5, "max": 4e-4, "default": 1.3e-4, "step": 5e-6, "unit_ucum": "m", "formatter": "scientific", "palette_role": "double-slit aperture" },
{ "id": "L", "kind": "physics", "label": "screen distance L", "binds_to": "primitives[id=screen-intensity].parameters.screen_distance.value",
"min": 0.5, "max": 5, "default": 1.5, "step": 0.05, "unit_ucum": "m", "formatter": "fixed2", "palette_role": "scanning detector" }
],
"derived": [
{ "id": "fringe_sep", "label": "δx = λL/d (m)", "expr": "lam * L / d", "deps": ["lam", "L", "d"], "kind": "scalar" },
{ "id": "visibility_V", "label": "fringe visibility", "expr": "(b > 0 && d > 0) ? abs(sin(PI * b / d) / (PI * b / d)) : 0", "deps": ["b", "d"], "kind": "scalar" },
{ "id": "envelope_E", "label": "sinc² envelope", "kind": "array",
"expr": "(function(){ return x.map(function(xi){ var y_m = (xi - 0.5) * 0.025; var sa = PI * b * y_m / (lam * L); var s = abs(sa) 0 ? (0.5 * k0 * k0) / V0 : 0", "deps": ["V0", "k0"], "kind": "scalar" }
],
"readouts": [
{ "id": "ro-E", "label": "E =", "dynamic_value": { "compute_id": "kinetic_E", "format": "fixed2" }, "position": "under_canvas", "palette_role": "wavefunction curve" },
{ "id": "ro-V0", "label": "V₀ =", "dynamic_value": { "compute_id": "V0_val", "format": "fixed1" }, "position": "under_canvas", "palette_role": "barrier region" }
],
"panels": [
{ "id": "rho-panel", "kind": "field_2d", "label": "Probability density |ψ|²",
"axes": { "x_label": "x" },
"extras": { "show_legend": false, "fill_under": true },
"traces": [
{ "id": "tr-rho", "label": "|ψ|²", "source": "frames", "primitive_id": "crank_nicolson_1d", "state_key": "rho",
"palette_role": "wavefunction curve", "style": "fill", "fill_opacity": 0.32, "shadow_blur_px": 8, "shadow_color_role": "wavefunction curve",
"stroke_width_px": 1.4, "z_order": 0 }
]
}
],
"annotations": [
{ "id": "ann-V0", "type": "math_label", "anchor": { "space": "normalized", "x": 0.5, "y": 0.06 },
"text_template": "V₀ = {value}",
"dynamic_value": { "compute_id": "V0_val", "format": "fixed1" },
"palette_role": "barrier region" },
{ "id": "ann-E", "type": "math_label", "anchor": { "space": "normalized", "x": 0.85, "y": 0.06 },
"text_template": "E = {value}",
"dynamic_value": { "compute_id": "kinetic_E", "format": "fixed2" },
"palette_role": "wavefunction curve" }
],
"equations": [
{ "id": "eq-schroedinger", "latex": "i\\hbar \\partial_t \\psi = -\\tfrac{\\hbar^2}{2m}\\partial_x^2 \\psi + V(x)\\psi",
"role": "governing", "palette_role": "wavefunction curve", "position": "caption", "size": "medium" }
],
"camera_presets": [],
"interactions": {
"play_button": true, "scroll_lock": false, "drag_camera": false, "reset_button": true,
"layer_toggles": []
},
"primitive_bindings": [
{ "primitive_id": "crank_nicolson_1d", "state_key": "rho", "palette_role": "wavefunction curve" }
]
}
]
}
9. R1-R10 enforcement
- R1 anti-hardcode: every numeric in viz_spec originates from simspec/claims, not paper copy-paste.
- R2 DAG completeness: every derived[] declares deps[] for every variable referenced in expr.
- R3 trace source consistency: source=analytic → computeid (kind=array); source=frames → primitiveid + state_key.
- R4 palette coverage: every paletterole referenced is a key in palettemap.
- R5 layertoggle.traceids must reference real panel.traces[].id values.
- R6 readout.dynamicvalue.computeid MUST be a kind:"scalar" derived; trace.compute_id MUST be kind:"array".
- R7 critic-loop friendly: refinement suggestions edit specific derived/trace/readout entries without restructuring untouched fields.
- R8 swept_param matches sliders[].id OR derived[].id (V8 extension).
- R9 frames require binding (panel.traces[] with source=frames OR primitive_bindings[]).
- R10 no duplicate ids inside panels[], derived[], readouts[], or layer_toggles[].
10. Generation protocol (5 steps)
- Read physics.json governing equations. Identify observable quantities.
- Identify panels[] from figures.json. Assign layout.rows/cols.
- Build derived[] DAG leaf-to-root. Scalar vs array kind.
- For each panel, assign traces[] referencing derived ids or primitive state_keys.
- Build readouts[] from claims.json predicted values, annotations[] from figure markup.
11. Refusal case
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: leventilo
- Source: leventilo/mobius
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.