| Path | Purpose |
|---|---|
| README.md | Project overview and quick start |
| AGENTS.md | AI agent coding rules and pointers |
| CLAUDE.md | Claude Code rule manifest (loads style docs) |
| VERSION | CalVer version string |
| LICENSE.LGPL_v3 | Code license |
| LICENSE.CC_BY_4_0 | Content license |
| build_github_pages.sh | Canonical production build into dist/ |
| run_web_server.sh | Build then serve dist/ on local network |
| check_codebase.sh | Aggregate lint, typecheck, and test gate |
run_fast_checks.sh |
Umbrella fast gate: build, check_codebase.sh, pytest, content validation (no browser sweep) |
| run_playwright_tests.sh | Front door for every browser test: build as needed, then run npx playwright test against playwright.config.ts (.spec.ts files, including the protocol walker sweep spec) |
| source_me.sh | Bash environment for Python 3.12 |
| package.json | Node dev dependencies and npm scripts |
| tsconfig.json | Repo-root TypeScript compiler config (strict) |
| tsconfig.lint.json | Wider typecheck covering tests/ and tools/ |
playwright.config.ts |
Playwright test-runner config: testDir: tests/playwright, testMatch: **/*.spec.ts, single chromium project, webServer builds then serves dist/ on one shared random port for every worker |
| pip_requirements-dev.txt | Python dev dependencies (pytest, pyflakes) |
| eslint.config.js | ESLint flat config |
| REPO_TYPE | Repo type marker (typescript) |
Browser entry points, scene runtime, and shell HUD. All authored; nothing
under src/ is generated.
src/
+- dist_entry.tsx -- bundle entry; DOM-presence router
+- launcher_entry.tsx -- launcher bundle entry
+- protocol_host_entry.tsx -- protocol-host bundle entry
+- protocol_host.tsx -- wires pipeline, renderer, step machine, HUD
+- index.html -- bench page (smoke target)
+- scene_viewer_template.html -- scene viewer HTML template (copied to dist/scene_viewer.html)
+- style.css -- stylesheet (copied to dist/)
+- launcher/
| +- index.html -- launcher HTML page
| +- Launcher.tsx -- Solid protocol-selector component
| `- main.tsx -- launcher Solid mount
+- scene_runtime/
| +- layout/ -- multi-pass layout pipeline
| | +- run_pipeline.ts -- top-level pipeline runner
| | +- types.ts -- PipelineResult, ComputedItem, layout types
| | +- constants.ts -- DEFAULT_VIEWPORT, shrink factor
| | +- phases.ts -- phase registry: named phase sequence with read/mutate boundaries
| | +- geometry/ -- pure 2D AABB geometry core (no layout state)
| | | +- types.ts -- Vector, Aabb, Collision, ResolutionCandidate value types
| | | `- collision.ts -- aabbFromBounds, detectCollision, buildResolutionCandidate, sortResolutionOrder
| | +- config/ -- LayoutConfig resolution (global defaults -> scene -> zone -> strategy)
| | | +- types.ts -- LayoutConfig and related config types
| | | +- resolve_config.ts -- config precedence resolver
| | | `- index.ts -- barrel export
| | +- diagnostics/ -- severity-graded typed diagnostics and decision metadata
| | | +- severity_model.ts -- Error / Warning / Review-required severity types
| | | +- payload.ts -- typed diagnostic payload shapes
| | | +- decision_metadata.ts -- per-scene decision metadata (strategy, packer, shrink, config)
| | | +- offcanvas.ts -- off-canvas classifier: fully_off_canvas / partial_overflow (report-only, never gates)
| | | `- index.ts -- barrel export
| | +- strategies/ -- PlacementStrategy seam and implementations
| | | +- placement_strategy.ts -- PlacementStrategy interface
| | | +- row_strategy.ts -- default row strategy
| | | +- pack_strategy.ts -- overflow packer (engages below threshold or on overflow)
| | | `- index.ts -- barrel export
| | `- (bind_objects, normalize_schema, resolve_inheritance, scale_to_real_world,
| | group_by_zone, horizontal_layout, vertical_layout, layout_labels,
| | clamp_scene_bounds, footprint, wrap_label, index).ts
| +- protocol/ -- step machine and protocol drivers
| | +- affordance.ts -- pure affordance-kind mapping (compute_affordance_kind, affordance types)
| | +- gesture_registry.ts -- GESTURE_REGISTRY: per-gesture affordance contract + dispatch_gesture, scene_click_to_command
| | +- resolve_entry_scene.ts -- entry-scene resolution + empty-scene guard
| | +- target_adapter.ts -- protocol-target -> DOM identity adapter (resolve_to_placement/object, AmbiguousTargetError)
| | +- flatten_sequence_runner.ts -- flattens a sequence_runner into its constituent mini-protocol steps
| | +- step_machine.ts -- pure step machine (no DOM)
| | +- validators.ts -- interaction and step validator dispatch
| | +- authored_value_check.ts -- load-time authored-value guard for target_with_value / final_state_matches
| | +- gesture_affordance_check.ts -- load-time invariant: authored gesture must be registered + wired (UnaffordancedGestureError)
| | +- target_existence_check.ts -- load-time invariant: authored target must exist in the scene
| | +- scene_operations.ts -- routes the five SceneOperation primitives to injected store-backed deps
| | +- scene_op_deps.ts -- SceneOpDeps interface + store-backed factory
| | +- walker_debug.ts -- debug helpers for walkthrough evidence collection
| | +- click_resolver.ts -- DOM click -> step machine
| | `- emitter.ts -- ProtocolShellEmitter, RuntimeEmitterHandle
| +- state/ -- Solid signal store for shared scene state
| | `- scene_store.ts -- createSceneStore: object state signals + updaters
| `- renderer/ -- Solid DOM rendering from PipelineResult
| +- render_scene.tsx -- public Solid mount facade (mounts SceneView)
| +- affordance_candidates.ts -- renderer-layer candidate enumeration (enumerate_candidate_targets)
| +- scene_view.tsx -- Solid SceneView (bg, items, labels, guards)
| +- scene_item.tsx -- Solid SceneItem (per-item + missing-svg placeholder)
| +- visual_state_resolver.ts -- state + visual_states -> renderable description
| +- render_background.ts -- background (gradient or asset)
| +- structural_guards.ts -- six layout validation guards
| +- inject_svg.ts -- inline SVG injection from ASSET_SPECS
| +- svg_manifest_loader.ts -- runtime SVG manifest fetch/cache layer
| `- index.ts -- barrel: renderScene, mountScene, SceneView, SceneItem
`- shell/
+- adapter/
| `- types.ts -- closed seam: ProtocolConfig, ShellViewSnapshot, events, ops
+- signals.ts -- Solid signal helpers + subscribeEmitterToSnapshot
+- hud/
| +- ProtocolHud.tsx -- mounts four region components into named DOM targets
| +- type_input.tsx -- visible type-input affordance (data-type-input / data-type-commit)
| `- set_point_editor.tsx -- shared numeric set-point editor for the adjust gesture (data-adjust-input / data-adjust-commit)
`- regions/
+- StepOutline.tsx -- read-only ordered step cards
+- TipsBubble.tsx -- professor-tip bubble
+- StepCounter.tsx -- completed/total counter
`- GuidanceBar.tsx -- current-step prompt
The framed interface uses six named DOM regions in src/protocol_host_template.html:
header (tips + counter), scene (bounded 16:9 panel), outline (step list),
and guidance (teal prompt bar). #shell-root is a sibling of #scene-root,
never an ancestor.
Every script that emits to generated/, assembles bundles, or produces
dist/ artifacts. Invoked by package.json pre-hooks and
build_github_pages.sh.
| File | Purpose |
|---|---|
| gen_object_library.py | content/objects/ YAML -> generated/object_library.ts |
| gen_svg_manifest.py | assets/**/*.svg -> generated/svg_manifest.ts (asset_name -> relative file path) |
| gen_scene_index.py | Scene YAML -> generated/scenes.ts + generated/scene_manifest.json; `--missing-svg=strict |
| gen_protocols.py | Protocol YAML -> generated/protocols.ts + generated/protocols_index_slim.ts + generated/protocol_materials.ts (per-protocol material registry from each package materials.yaml) |
| entity_decode.py | Codegen helper: decode_entities() maps authored HTML entities (µ etc.) to Unicode glyphs at emit time; imported by gen_protocols.py and gen_object_library.py so generated/** carries the glyph while source stays ASCII |
| gen_flow_view.py | Protocol YAML -> generated/flow_views/<protocol_name>.txt, a per-protocol audit view (click path, gestures, state changes, transitions); not the design source, see PROTOCOL_AUTHORING_GUIDE.md |
| build_protocol_index.py | Protocol index build helpers |
| list_protocols.py | Reads PROTOCOLS_INDEX; emit writes one dist/<name>.html per protocol |
| scene_inheritance.py | Scene YAML inheritance resolution library (imported by gen_scene_index) |
| build_main_bundle.mjs | esbuild Node API: bundles launcher and protocol-host entries |
| precompute_layout.mjs | Runs the layout engine for every scene at canonical 16:9 (1920x1080) -> generated/precomputed_layout.ts ({ final: ComputedItem[] } per scene). Runs after build_generated.sh. |
All authored YAML; nothing under content/ is generated or gitignored.
content/
+- objects/ -- reusable object definitions by kind
| +- bottle/, equipment/, flask/, pipette/, plate/, rack/, waste/, decoration/
+- base_scenes/ -- shared base scenes (bench, hood, microscope, etc.)
+- protocols/
| +- cell_culture/ -- cell culture mini-protocols and runners
| +- sdspage/ -- SDS-PAGE mini-protocols and runners
| `- runners/ -- sequence-runner protocols
+- objects_quarantine/ -- quarantined objects pending restoration review
`- base_scenes_quarantine/ -- quarantined base scenes pending restoration review
Each mini-protocol folder contains:
content/protocols/<cluster>/<protocol_name>/
+- protocol.yaml -- protocol_type, entry_step, learning block, steps
+- materials.yaml -- material definitions for this protocol
`- scenes/ -- per-protocol scene YAML overrides
Standalone Python validators. Entry point: validate.py.
validation/
+- validate.py -- aggregate entry: runs every validation stage
+- yaml_schema/ -- schema + cross-field rules for protocol, object, scene YAML
+- stepper/ -- step-flow walker: simulates execution, checks semantics
+- svg/ -- SVG asset usage audit
+- manual/ -- human-readable protocol manual renderer
+- scene_lint/ -- pre-render failure predictor (Group A BLOCKED, Group B advisory)
+- scene_design/ -- composition scorecard (weighted metrics, advisory only)
+- scene_calc/ -- thin loader of rendered geometry (stats.json) for lint and design; no layout math
+- structure/ -- layout structural check
`- shared_toolkit/ -- discovery, YAML I/O, findings, reporter, CLI helpers
Three tiers isolated by conftest.py
(collect_ignore = ["e2e", "playwright"]).
tests/
+- conftest.py -- pytest config; collect_ignore for e2e and playwright
+- test_*.py -- fast pytest: pyflakes, ASCII, indent, shebang, imports, etc.
+- test_*.mjs -- Node unit tests (run by node --import tsx --test)
+- TESTS_README.md -- test-suite overview
+- file_utils.py -- shared REPO_ROOT helper
+- e2e/ -- non-browser E2E runners (e2e_*.py, e2e_*.sh)
`- playwright/ -- browser-driven tests, runner model (@playwright/test)
+- repo_root.mjs -- shared REPO_ROOT resolver
+- smoke.spec.ts -- broadest test: launcher loads, a protocol card opens a scene
+- test_*.spec.ts -- runner specs (framed layout, initial scene, etc.)
+- helper_*.mjs / .tsx -- non-test support (scene discovery, render harnesses)
`- e2e/ -- full-path walkthroughs
`- protocol_walkthrough.spec.ts -- one test() per curriculum protocol (native
Playwright workers) plus a wrong-order negative test
playwright.config.ts (repo root) owns testDir: tests/playwright, testMatch: **/*.spec.ts, and the shared webServer (builds then serves dist/ on one
random port for every worker). run_playwright_tests.sh is the front door for
every browser test (npx playwright test); super_all_tests.sh's browser
step is a single call to it.
Key pytest files:
| File | Purpose |
|---|---|
| test_pyflakes_code_lint.py | Pyflakes lint gate |
| test_ascii_compliance.py | ASCII source check |
| test_markdown_links.py | Markdown link validity |
| test_shebangs.py | Shebang consistency |
| test_import_dot.py | Forbids relative imports |
| test_import_requirements.py | Third-party imports declared |
| test_test_naming_conventions.py | Test layout and naming |
| test_spec_docs_no_camelcase_yaml.py | Spec doc camelCase gate |
| test_walker_no_step_branches.py | Walker must not branch on step name |
Key Node test files:
| File | Purpose |
|---|---|
| test_layout_engine.mjs | Layout pipeline unit tests |
| test_step_machine.mjs | Step machine unit tests |
| test_structural_guards.mjs | Structural guard unit tests |
| test_resolve_entry_scene.mjs | Entry-scene resolution unit tests |
| test_visual_state_resolver.mjs | Visual-state resolver (formulas, materials, missing-svg) |
| test_scene_operations.mjs | Scene operations unit tests |
| test_protocol_emitter.mjs | Emitter unit tests |
| test_shell_signals.mjs | Shell signal binding tests |
| test_m2_integration.mjs | M2 framed-layout integration |
test_material_color.mjs |
D3 resolver contract: all resolve_color_result success and failure cases |
test_subpart_visual_state_renderer.mjs |
Subpart material-tint renderer: dispatch predicate, fill, transparent empty, degrade path |
| test_scene_store.mjs | Scene store: getSubpartStateField accessor, per-well independent reactivity |
test_material_acceptance_cross_layer.mjs |
Cross-layer acceptance: stepper D1 and TS runtime store accept and reject the same material names |
tests/test_layout_offcanvas.mjs |
Off-canvas classifier unit tests (exercises PipelineResult.offCanvasDiagnostics fully_off_canvas and partial_overflow paths) |
tests/test_layout_config.mjs |
Config-precedence unit tests: 16 behavioral tests covering zone_gap split, scene-level and zone-level overrides, and strategy-local values |
assets/equipment/ contains tracked source SVG files for all lab objects.
Sidecar *.colormap.json files group element ids for the recolor pipeline.
Processed by gen_svg_manifest.py.
Scripts that do not appear in any build chain. See CODE_ARCHITECTURE.md for the full list.
Key tools:
| File | Purpose |
|---|---|
tools/normalize_svg_v3.py |
SVG ingestion-gate normalizer (lxml + tinycss2 + shapely): normalize-or-reject pipeline; run before adding any SVG to assets/; see CODE_ARCHITECTURE.md for support contract and ingestion workflow |
| svg_to_html_render.mjs | Renders an SVG on five color swatches via Playwright Firefox and writes <stem>_render.{html,png} to CWD; use --no-open to skip auto-open |
| svg_identity_sweep.py | Perceptual-hash duplicate/mislabel sweep over assets/**/*.svg; emits a review report |
| svg_feature_census.py | Read-only feature census over the wild SVG corpus (OTHER_REPOS/); counts clipPath/transform/text/etc. per file, cross-tabbed against the v3 verdict; emits docs/active_plans/reports/svg_feature_census.{json,md} |
tools/layout_golden_diff.mjs |
layout:diff / layout:refresh -- ephemeral regression harness; captures a gitignored snapshot at test-results/layout_reference_snapshot.json with provenance and staleness detection; compares engine output after a change |
tools/layout_metrics.mjs |
layout:metrics -- raw per-scene geometry metrics (rectangle-union fill, largest-empty-rect, occupancy, scale proxies, AABB overlap graph, balance) with per-scene overlay |
tools/layout_health_report.mjs |
layout:health -- interprets raw geometry metrics into provisional health categories and a worst-first author scorecard; writes test-results/layout_health/ |
tools/offcanvas_baseline.mjs |
Writes docs/active_plans/audits/offcanvas_baseline.md (per-scene off-canvas item counts from PipelineResult.offCanvasDiagnostics) |
| File | Purpose |
|---|---|
| setup_playwright.sh | Idempotent Playwright (chromium) install |
| setup_typescript.sh | TypeScript dev environment setup |
| dist_clean.sh | Wipe generated/ and dist/ |
| rotate_changelog.py | Changelog rotation (keeps two newest day blocks) |
| query_changelog.py | Changelog search by date, category, keyword |
| commit_changelog.py | Draft commit message from new changelog entries |
| bump_version.py | Version bump helper |
| ai_polish_review.mjs | AI visual-polish reviewer over before/after scene screenshots |
| File | Purpose |
|---|---|
| NEWS.md | Curated release highlights and announcements |
| CHANGELOG.md | Chronological record of changes |
| CODE_ARCHITECTURE.md | System design, components, and data flow |
| FILE_STRUCTURE.md | This file |
| INSTALL.md | Prerequisites and setup |
| USAGE.md | Build and run instructions |
| AUTHORS.md | Maintainers and contributors |
| ROADMAP.md | Planned work |
| TODO.md | Backlog scratchpad |
| E2E_TESTS.md | E2E test conventions |
| PLAYWRIGHT_USAGE.md | Playwright browser-test usage |
| PYTEST_STYLE.md | Pytest conventions |
| PYTHON_STYLE.md | Python conventions |
| TYPESCRIPT_STYLE.md | TypeScript conventions |
| MARKDOWN_STYLE.md | Markdown formatting rules |
| REPO_STYLE.md | Repo-wide conventions |
| LAYOUT_REMAINING_WORK.md | Scene-by-scene layout and aesthetic remaining work reference |
| PRIMARY_CONTRACT.md | Hard design invariants |
| PRIMARY_DESIGN.md | Design philosophy |
| PRIMARY_SPEC.md | Technical specification |
| CLAUDE_HOOK_USAGE_GUIDE.md | Claude Code permissions hook reference |
| specs/PROTOCOL_VOCABULARY.md | Canonical protocol vocabulary |
| specs/PROTOCOL_YAML_FORMAT.md | Protocol YAML schema reference |
| specs/PROTOCOL_AUTHORING_GUIDE.md | Worked authoring example |
| specs/PROTOCOL_STEPS.md | Step-flow architecture |
| specs/SCENE_VOCABULARY.md | Canonical scene vocabulary |
| specs/SCENE_YAML_FORMAT.md | Scene YAML schema reference |
| specs/SCENE_ARCHITECTURE.md | Scene wiring and runtime |
| specs/SCENE_METRICS.md | Scene-author guide to the layout health report |
| SCENE_LAYOUT_BASELINE.md | Committed, hand-refreshed layout health/diagnostics snapshot taken after the failBuild gate went live |
| specs/OBJECT_VOCABULARY.md | Canonical object vocabulary |
| specs/OBJECT_YAML_FORMAT.md | Object-definition YAML schema reference |
| specs/LAYOUT_ENGINE.md | Layout-engine placement reference |
| specs/MATERIAL_DESIGN.md | Design rationale for scalar color, transparent empty, and the separate identity/amount layers |
| specs/MATERIAL_VOCABULARY.md | Canonical material terms, the settled sentinel/visible classification, and the D1 predicate entry point |
| specs/MATERIAL_YAML_FORMAT.md | materials.yaml schema: entry keys, scalar display_color hex format, D1 predicate, closed-key rule, sentinel exemption |
| specs/MATERIAL_CONVENTION.md | Runtime render-effect and target convention, color resolver typed result, SVG anchor contract, and recolor model |
MATERIAL_LINT.md |
Validator and audit surface: lint rules L1-L10, D3 resolver contract, cross-YAML agreement rule, and validator hook table |
| specs/SVG_PIPELINE.md | SVG asset ownership and pipeline |
| specs/SCALING_MODEL.md | Scaling model notes |
| specs/WALKTHROUGH_GUIDE.md | Real-browser protocol walkthrough guide |
| specs/SPEC_DESIGN_CHECKLIST.md | Author YAML vocabulary lock checklist |
active_plans/ |
In-flight plan documents (active, audits, reports, decisions, workstreams) |
archive/ |
Archived plans and design notes |
All gitignored (see .gitignore):
| Path | Source script |
|---|---|
generated/object_library.ts |
gen_object_library.py |
generated/svg_manifest.ts |
gen_svg_manifest.py |
generated/svg_placeholder_keys.ts |
gen_svg_manifest.py (build/test-only placeholder key array) |
generated/scenes.ts |
gen_scene_index.py |
generated/scene_manifest.json |
gen_scene_index.py (per-scene classification, source of truth for scene tooling) |
generated/protocols.ts |
gen_protocols.py |
generated/protocols_index_slim.ts |
gen_protocols.py |
generated/protocol_materials.ts |
gen_protocols.py (per-protocol material registry; keyed by protocol_name) |
generated/flow_views/<protocol_name>.txt |
gen_flow_view.py (per-protocol audit view; not the design source) |
generated/precomputed_layout.ts |
precompute_layout.mjs (PRECOMPUTED_LAYOUT: per-scene { final: ComputedItem[] } at canonical 16:9) |
generated/scene_render_stats/<scene>.stats.json |
renderer-produced scene geometry stats (build evidence consumed by SCENE-LINT/SCENE-DESIGN), written by build_github_pages.sh via scene_to_png.mjs |
dist/ |
build_github_pages.sh (GitHub Pages bundle) |
dist/assets/svg/<category>/<name>.svg |
SVG assets copied by build_github_pages.sh |
dist/scene_viewer.html |
Copied from src/scene_viewer_template.html during build |
test-results/ |
Playwright screenshots and reports |
test-results/scenes/<scene>.png, test-results/scenes/summary.json |
optional human artifacts (PNG screenshots and run report), written only with node tools/scene_to_png.mjs --all --png |
node_modules/ |
npm install output |
The generated/ tree is rebuilt from current YAML and SVG sources on every
build. Do not place authored files there.
| Question | Document |
|---|---|
| How is the system designed? | CODE_ARCHITECTURE.md |
| Where does each file live? | This file |
| What are the hard rules? | PRIMARY_CONTRACT.md |
| What is the YAML schema? | specs/PROTOCOL_YAML_FORMAT.md, specs/SCENE_YAML_FORMAT.md, specs/OBJECT_YAML_FORMAT.md |
| How do I author a protocol? | specs/PROTOCOL_AUTHORING_GUIDE.md |
| How do I set up the repo? | INSTALL.md |
| How do I run the game? | USAGE.md |
| What is planned next? | ROADMAP.md and TODO.md |
| Type | Location |
|---|---|
| New mini-protocol | content/protocols/<cluster>/<name>/ with protocol.yaml, scenes/, materials.yaml |
| New base scene | content/base_scenes/<name>.yaml |
| New lab object | content/objects/<kind>/<name>.yaml |
| New SVG asset | assets/equipment/<name>.svg (run tools/normalize_svg_v3.py first; plus optional <name>.colormap.json) |
| New pipeline generator | pipeline/ (register in package.json pre-hooks; update these two docs) |
| New shell region | src/shell/regions/ (mount in src/shell/hud/ProtocolHud.tsx) |
| New runtime module | src/ (imported from entry or scene runtime) |
| New validation rule | validation/yaml_schema/ or validation/scene_lint/ |
| Fast pytest test | tests/test_*.py |
| Node unit test | tests/test_*.mjs |
| Playwright browser test | tests/playwright/*.spec.ts (runner model; see playwright.config.ts) |
| Full-path walkthrough | tests/playwright/e2e/*.spec.ts |
| Non-browser E2E | tests/e2e/e2e_*.py or tests/e2e/e2e_*.sh |
| Developer utility | tools/ (never pipeline/) |
| Documentation | docs/ with SCREAMING_SNAKE_CASE filename |
| Spec vocabulary | docs/specs/ |
| Active plans | docs/active_plans/active/, audits/, reports/, decisions/, or workstreams/ |