The prototyping lab itself
This skill is about the lab, not about drawing in it. Read it when the task
is to extend or maintain the place rather than to write a sketch.
Where the lab is, and why it is not in git
~/Desktop/vncode-lab/runs/<runtime>/<slug>/ one folder per run
~/Desktop/vncode-lab/runs/catalog.json what the lab has made (seed, concept, files)
~/Desktop/vncode-lab/prints/ saved PNGs
~/Desktop/vncode-lab/apps/ compiled binaries
Resolution, in order:
DSH_PROTO_LAB_ROOT — an explicit root: an absolute path, or one relative to
the workspace (a standalone clone, a different project). Prints and apps then
live inside it;
- otherwise
~/Desktop/vncode-lab/ — because a prototype is a thing you open and
a print is a thing you look at, so both belong where a person can see them
rather than buried in a checkout;
- otherwise
<workspace>/lab on a machine with no Desktop at all (a container, a
CI runner). The lab still works there; it is simply not on a desktop.
The desktop folder is what lab_open prints, and it is the answer to "where did my
run go". A print is the one artifact a browser places itself: Save PNG writes
to the browser's Downloads folder under vncode-lab-<slug>-seed-<n>.png, so the
filename says which run and which seed made it and it can be moved into prints/
without guesswork. A compiled openFrameworks binary is built inside the run's own
bin/; apps/ is where copies for keeping belong.
/lab/ is in the repository's .gitignore, deliberately: the lab is a
workshop, not an exhibit. Runs are generated on this machine, they are throwaway
by design, and a repository that accumulated every sketch anyone tried would stop
being a plugin pack. What ships is the generator, the conventions and the example
library; what you make with them stays local - and now, on the desktop.
The consequence to hold on to: a run can be deleted and regenerated, and that
is not a loss. lab_new derives a run's seed from its name (unless you pass
one), so lab_new runtime=p5js slug=flow-field gives the same first frame today
and in a year. If a run is worth keeping beyond that, promote it (below).
catalog.json is a convenience, never the truth. The lab is what is on disk; the
catalog records the seed, the concept and the files of what was made. Deleting
the catalog costs you the seeds, not the runs.
Promoting a run into the example library
The example library is the part meant to grow — it is the set of runs that are
committed, checked, and instantiable, and it is where a good prototype goes
when it stops being an experiment.
An example lives beside the skill that teaches its runtime:
packages/dsh-proto-lab/skills/<runtime>-prototypes/
SKILL.md
examples/<slug>/
example.json { slug, label, concept, seed, params }
sketch.js (p5js) — or src/ofApp.h, src/ofApp.cpp (openframeworks)
To promote one:
- copy the run's prototype file(s) from the lab into
examples/<slug>/ — the
PROTOTYPE only, never the generated shell, the library or the README;
- write
example.json: the slug, a label, the one-sentence concept, the seed,
and params as prose naming what each number does;
- add a row to the table in that skill's
SKILL.md;
- run the package's checks (
npm run check in packages/dsh-proto-lab). The
examples check drives every example, so an example that does not lint or does
not render fails there — which is the point of a library.
Generated files are never part of an example: the shell, p5.min.js and
README.md are written by the runtime at instantiation, so an example that
carried its own copy would be a second thing to keep in step. That is why
lab_new action=example regenerates them.
Adding a runtime — one module, one line
A runtime is a language. The lab owns half of it already (the folder, the
catalog, the path policy, the size caps, the README and NOTES), and a runtime
owns the other half:
lib/runtimes/<id>.js the module: detect, scaffold, materialize, lint, examples
lib/runtimes/templates/*.template the source a new run starts from
lib/runtimes/index.js add it to RUNTIME_MODULES - one line
skills/<id>-prototypes/SKILL.md and a skill that teaches it
lib/runtimes/_template.js is the contract with the blanks filled in; copy it.
The five things it asks for:
| function | what it must answer |
|---|
detect({ env }) | is this runtime's toolchain here, where, and what to set if not — never throws, runs on every lab_runtimes |
scaffold(spec) | the files a NEW run gets: { path, content }[], paths relative, {{TOKEN}} placeholders substituted for you |
materialize(example) | the files an EXAMPLE turns into: its own bytes, plus the generated shell/library/documents |
lint(run) | is a run still a prototype in this language: { severity, code, message, file }[], errors vs warnings |
examples({ kitDir }) | the committed examples beside the skill, read from example.json |
Rules that are not negotiable in a new runtime:
- No network at run time. Vendor what a run needs (p5's
p5.min.js sits in
lib/vendor/, built and hash-checked by vendor/build.mjs) or find the user's
own install (openFrameworks). Never a CDN.
- No binary is ever committed or shipped. A
.gitignore per run keeps build
output out; a runtime that generates a project also says which command builds
it.
lint must not claim more than it checked. If the toolchain is missing,
the answer says not-compiled rather than implying the source was verified.
detect is cheap. It runs on every listing; a probe that compiles
something is not a detection.
Two credible next runtimes, and what each one actually costs:
- Processing (Rust) — a
.pde-like sketch in Rust: a main.rs, a
Cargo.toml pinning nannou or rust-skia, and a cargo run recipe. The
toolchain probe is cargo --version; the lint is cargo check when cargo
exists.
- Cinder — C++ like openFrameworks, so it is closer to what is already here:
three files, a CMake project, and
cmake --build as the recipe.
Neither is registered today, and that is the honest state: a runtime whose
examples have never been compiled would be a language the lab offers and cannot
check. lib/runtimes/_template.js exists so the day it is worth doing is a small
day's work.
Judging the lab
lab_lint with no arguments lints every run and reports only problems, which is
the check to run before saying the lab is in good shape. Errors are broken;
warnings are things that are not what this lab does — a stray file, a run missing
its window.__LAB.drew, a catalog entry whose file list has drifted from the
folder.
lab_open is the other end: it prints the absolute root, where the root came
from, every run on disk with its size, and whether the catalog agrees. Start
there when something is not where you expect it.