AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Viz Mapper

skill-leventilo-mobius-viz-mapper · by leventilo

Translate a verified SimSpec into a typed viz_spec.json conformant to the V8 viz_spec.schema.json. Output JSON only — no HTML, no CSS, no JavaScript. Server-side ajv validation enforces shape post-receive (grammar path is currently OFF — schema exceeds Anthropic compiled-grammar size cap). The frontend renderer (public/runtime/render.js) consumes viz_spec + frames + narration + provenance and ren…

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-leventilo-mobius-viz-mapper

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-leventilo-mobius-viz-mapper)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Viz Mapper? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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 narration skill 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) — never Math.sin. For kind: "array" the expr returns a length-N array, typically via (function(){ return x.map(xi => ...); })(). The implicit x is a Float32Array of length default_grid_n on [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_kindsimspec.simulation.evolution_kind (static | time_series | parameter_sweep).
  • state_source: frames for primitive-driven series; analytic for closed-form.
  • swept_param: required iff parameter_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:

  1. 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(...) ...).
  2. Build array derived for plotted quantities. The implicit x is 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; }); })().
  3. List every slider id and every other derived id referenced in deps[].
  4. Final node consumed by a trace must be kind: "array".
  5. 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. The function keyword + => 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_id referencing a kind: "array" derived.
  • source: "frames" + primitive_id + state_key referencing simspec.primitives[].state.
  • palette_role → palette_map.
  • style: line (default), fill (Schrödinger ψ²), dots, bars, area.
  • shadow_blur_px + shadow_color_role for emphasis (Young measured-detector trace).
  • stroke_dash_pattern: [4, 4] for envelope/reference curves.
  • z_order for 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)

  1. Read physics.json governing equations. Identify observable quantities.
  2. Identify panels[] from figures.json. Assign layout.rows/cols.
  3. Build derived[] DAG leaf-to-root. Scalar vs array kind.
  4. For each panel, assign traces[] referencing derived ids or primitive state_keys.
  5. 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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.