# Code Observatory: execution you can inspect

Research and implementation notes, 21 October 2026. This app runs a small local teaching language named **MiniScript**. It is not a Python or JavaScript interpreter.

## Primary references

1. [Philip J. Guo, Online Python Tutor, SIGCSE 2013](https://pg.ucsd.edu/publications/Online-Python-Tutor-web-based-program-visualization_SIGCSE-2013.pdf). The original paper describes moving forward and backward through recorded execution, inspecting the active line, variables, stack frames and output. This supports keeping source, memory and control flow linked to one actual execution state. Code Observatory uses its own bounded interpreter and original interface; it does not reproduce Python Tutor's language or heap model.
2. [Python Tutor](https://pythontutor.com/). The project's current teaching guidance includes asking learners to predict what happens at a step. Our prediction questions are calculated from the current MiniScript values, including edits, rather than keyed to a canned story answer.
3. [Scratch's if/else block reference](https://scratch.mit.edu/help/studio/tips/blocks/if-else/) and [Scratch Foundation's conditional statements guide](https://www.scratchfoundation.org/learn/learning-library/conditional-statements). These explain choosing behavior from a true/false condition and suggest creative, interactive uses. Our original weather-bag example uses the same basic decision concept; it does not import Scratch blocks or execute Scratch projects.
4. [CS50's Introduction to Programming with Python, lecture 2](https://cs50.harvard.edu/python/notes/2/). Its loop and list lessons connect repeated actions, changing counters, conditions and array length. This informs showing the counter and total separately, distinguishing loop visits from executed instructions, and explicitly teaching zero-based indices. The MiniScript syntax is different from the Python examples in that course.
5. [CS50x, Debugging: Step through](https://cs50.harvard.edu/x/2024/shorts/debugging_step_through/). This official debugging lesson supplies a reference for deliberately inspecting an execution one step at a time. The original extra-lunch-box exercise in this app stops at a real array-boundary error and can be corrected by changing `<=` to `<`.

## Design predictions, not measured learning outcomes

- Familiar stories should make a variable's purpose easier to recall: coins for subtraction, snack bags for counters, lockers for arrays, gardens for functions and books for recursion. Test with learners by asking them to explain a new edited example without replaying the original narration. No learner study has been conducted here.
- Before/after values beside the executed instruction should help distinguish assignment from a permanently linked spreadsheet formula. Test by asking what happens after changing an input without rerunning the calculation.
- A separate call workspace should help learners distinguish local values in recursive calls. Test by asking which `n` belongs to the waiting call before a return.
- Distinguishing a condition check, a loop-back move and a body instruction should reduce confusion about why one source line is visited more than once. Instruction counts are not loop-iteration counts or processor timings.
- An intentional off-by-one error should make array bounds observable. The learner can predict three outputs, inspect the failing fourth access, edit one comparison, and verify successful completion with the same outputs.
- Optional visual depth and animation are presentation aids. Their educational value must be evaluated separately; they must never determine the execution result or hide the accessible source, memory or output.

## Original experiment library

| ID | Experiment | Controlled change | Observable result / boundary case |
| --- | --- | --- | --- |
| pocket-money | Save for a day out | Ticket cost | Savings change from 12 to 8 when cost changes from 8 to 12. |
| umbrella | Pack for the weather | `raining` true/false | Only the selected message is printed. |
| sum | The running total | Final day | 5 days gives 15; 6 gives 21; a limit of 0 never enters the loop. |
| snacks | Pack three snack bags | Bags and crackers per bag | Three bags of four print 4, 8, 12; zero bags print nothing. |
| search | Find the signal | Target in a fixed array | 7 appears at index 3; absent 13 produces `Not found`. |
| function | Measure two gardens | Call arguments | 6 × 4 returns 24; 3 × 5 returns 15; a zero side returns 0. |
| factorial | A function within itself | Distinct book count | 5 gives 120; 3 gives 6; the `n <= 1` base case stops further calls. |
| debug | The extra lunch box | `<` versus `<=` | Three valid outputs precede an index-3 error; the corrected comparison completes. |

The previous `sum`, `search` and `factorial` IDs and their default results are retained. Examples are editable MiniScript programs, not prerecorded animations. A changed source program must be loaded before execution.

## Execution and explanation contract

`model.js` owns parsing, expression semantics and execution. `learning.js` receives the snapshot before a step, the snapshot after it, and the instruction captured before execution. It derives explanations from actual assignments, frame pushes/pops, emitted output and condition evaluation. It never treats an animation as evidence of execution.

Prediction and substitution calculations reuse the model's expression evaluator with an isolated copy of the current state. They do not run user JavaScript or mutate the live machine. A user-function call prediction asks about its first input value, not an uncomputed return. Runtime expression errors produce a check-the-inputs prediction; completed runs have none.

The educational model has named values, numbers, strings, booleans, null, arrays, expressions, if/else, while, named functions, calls, returns and printed output. User-function calls belong on a direct assignment line. Array indices are zero-based and are read-only in this language. Function locals are frame-scoped. Arrays are copied by the interpreter, so they must not be shown as shared Python objects or actual memory addresses.

The sandbox bounds program length, nesting, numbers, strings, arrays, instructions, stack depth, stored state and rewind history. These are browser protection limits, not limits of programming in general. Recursion's book-count story assumes distinct books and nonnegative integer counts. This tool is not a benchmark, debugger for other languages, remote code executor or substitute for testing a production program in its actual runtime.

## Verified results

On 21 October 2026, `node --test apps/code-observatory/tests/learning.test.cjs` passed **14 tests**. The suite executes every shipped source through the real interpreter and verifies its declared outputs/status. It checks edited and zero-input examples, the intentional boundary error and correction, arithmetic before/after explanations, actual-value predictions, short-circuit conditions, loop/branch routing, recursive call/return ownership, global assignments inside a function, array-error guidance, state immutability, stopped states and operator-like string literals.

Actual test totals and browser acceptance findings are appended after the integrated app review. The design predictions above remain hypotheses even when implementation checks pass.

### Integrated verification — 21 October 2026

`node --test apps/code-observatory/tests/*.test.cjs apps/shared/tests/catalog.test.cjs` passed **60 Node test entries** with no failures: 14 controller cases, 14 learning cases, 13 rendering cases, 17 session cases, the interpreter regression file and the shared catalog audit. The interpreter file contains multiple assertion-based scenarios under one test entry. The catalog audit checks all 11 routes and thumbnails plus 201 local references.

Browser verification exercised the real interface: running total completed with 15; Back removed the printed output and timeline Home returned to empty memory; a line-5 pause point stopped before each loop visit; prediction feedback was calculated before executing the next instruction. The intentional array-boundary lesson printed three fruits and stopped at index 3; changing `<=` to `<` finished with those same three messages. Recursion showed six distinct call trays before the base-case return. Saving/loading restored pace and code at step zero while retaining a separate comparison across reload. Focus view, phone panel switches and both color themes were checked. The preview recorded no browser errors or warnings during these checks.

Responsive browser checks used 320×740, 390×844, 768×1024, 844×390 and 1440×1000 viewports. DOM measurements showed no horizontal page overflow at those sizes; touch buttons checked at phone width were at least 44 pixels high. Chart geometry now follows its visible width, retaining readable axis labels on narrow screens. These are browser viewport checks, not tests on physical phones or a measured accessibility/user-learning study.

The final screenshot is used by the Lab catalog. The preview remains local on port 4173. No publication or external code execution is involved.

### Editable template library — 21 October 2026

The library now contains **24 templates**, preserving the original eight sources and IDs. The 16 additions are temperature conversion, hours/remainders, bill sharing, even/odd counts, discounts, countdowns, multiplication tables, array sums, maximum scores, arithmetic means, matching-item counts, text reversal, palindromes, prime checking, Euclid's greatest common divisor and iterative Fibonacci. Search matches the title, topic, story and source. Topic and learning-level filters combine, with a clear empty state and reset action.

Each card can load its source for observation or open it directly in the editor. The native textarea has a synchronized numbered gutter, cursor position, current-line/error markers, optional wrapping and two-space Tab / Shift+Tab indentation. Escape followed by Tab leaves the editor. Ctrl/Cmd+Enter applies the draft; Apply & play begins playback unless prediction mode is enabled. Applying starts a fresh trace, while invalid code retains the editable draft and the previous valid run. Editing a template never changes the library's original source.

`templates.js` stores executable source lines beside their learning purposes. `learning.js` adds a purpose only when the whole source exactly matches a shipped template; all edited programs retain explanations computed from actual values, decisions, calls and output without stale preset claims. Internal loop-back moves and completion remain distinct from condition checks and printing. The explanation panel starts expanded.

The integrated suite now passes **113 Node test entries**: 18 controller, 10 editor, 18 learning, 13 renderer, 17 session, 35 new-template checks, the interpreter regression file and the shared catalog audit (204 local references). It executes every template and checks every compiled executable line, including branches absent from the default trace. Boundary edits cover empty arrays, zero divisors, invalid counts, negative-only maximums, prime boundaries, GCD zero cases and Fibonacci limits. A deliberately broken countdown reaches the interpreter's execution limit.

Browser checks verified search and combined filters, direct template editing, multiline indentation, keyboard application, error-line highlighting, keeping a previous valid result, and Apply & play. Editing Fibonacci's input from 7 to 10 produced 55. The average template's sixth step explained the actual addition 0 + 6 and highlighted index 0. Wrapped lines aligned with their gutter numbers; Escape then Tab reached the wrap control. Both themes were inspected, with no reported browser errors/warnings. Viewports of 320×740, 390×844, 768×1024 and 844×390 had no horizontal page overflow; phone editor text was 16px and the three editor action buttons were at least 44px high. These are browser checks, not physical-device or learner-study results.

### 3D execution, code hints and value histories — 21 October 2026

The workbench now opens in a real Three.js execution world. Named memory blocks, an instruction station, true/false gates, an indexed array close-up and an output display reflect the same interpreter state as the source, explanation and memory cards. Orbit, pan, top view, zoom, reset and an expanded viewer work without advancing the program. Every stored value remains available in the exact-value inspector, including values outside the visible close-up. Recursive calls have distinct frame IDs. This world is a teaching diagram rather than a physical model of computer memory.

Animated transfers are verified by replaying one MiniScript instruction on isolated state, recording the actual reads, writes, indexes, literals, arguments, return values and outputs, then checking the resulting state. This captures short-circuit decisions and same-value assignments without guessing from differences alone. Errors and backwards replay do not show successful forward writes. Flight duration follows playback pace; reduced motion and Run to end suppress flights. Hidden and offscreen scenes stop rendering. WebGL failure leaves the complete memory-card interface available. The scene preview displays at most four workspaces, six named blocks per workspace and twelve array items; notices and the inspector explain omitted content.

Every source token has a plain-language hover or tap explanation, and each line has a question-mark button for its purpose. Arrow keys inspect that line's symbols without creating hundreds of tab stops. Escape closes the tip; tap pins it. Popovers are clamped to the visible viewport, including scrollbars. Assignment and equality, remainders, brackets, string text, array indexes and short-circuit logic have distinct descriptions. Current variable values are labelled as current active-scope values, not historical readings. Exact-template purposes apply only while the complete source matches that template; edited code uses generic syntax explanations.

The new numeric history charts offer step lines, bars, actual current/minimum/maximum values and CSV export. Clicking a recorded point revisits its execution step. Separate function calls have separate tracks, with gaps before creation, after return, or when a value is not numeric. The existing charts still show call depth, named-value counts and printed-message counts. A Charts link next to the viewer leads directly to this section. These charts describe program execution, not processor speed.

A session registry bounds numeric history to 64 distinct tracks and 48 numeric samples per step. Metadata is stored once; samples contain compact IDs and numbers. A permitted 19,000-character variable name over 3,001 trace entries previously duplicated about 114 MB of numeric metadata in the initial implementation; the corrected representation uses about 75 KB of samples plus 19 KB of metadata. Missing chart samples are disclosed, while exact current memory remains accessible and exportable.

Integrated verification passes **212 Node test entries**, including 51 scene-model checks, 14 scene-layout/animation checks, 19 code-hint checks and 12 numeric-history checks. Every step of all 24 supplied templates is covered by the scene model. Regression checks include true reads, calls/returns, array identity, short-circuit expressions, boundary failures, backwards replay, repeated redraws, registry limits and long names. The catalog audit verifies all 11 app routes and thumbnails and 211 local resource/link references.

Browser review checked actual WebGL rendering, indexed array highlights, recursive scope IDs, camera top/zoom/reset controls, expanded and memory-card views, line/symbol tips, tap and keyboard dismissal, step-line and bar charts, chart-to-timeline replay and the Charts shortcut. The running total reached 15 and the donation array reached 14. Both themes were inspected. Layout checks at 320×740, 390×844, 768×1024, 844×390 and the normal desktop viewport found no horizontal page overflow; the 320px tooltip remained between x=10 and x=295 within a 305px client width. No browser errors or warnings were recorded. These are automated and browser viewport checks, not physical-device or learner-study results.

### Clear value-flow replay — 21 October 2026

The 3D workbench now reserves a larger share of the desktop width and adds an explicit Read → Work → Destination explanation of the current instruction. The destination wording follows the instruction: store a value, display a message, enter a function, return an answer or follow a condition. Animation is a replay of the completed instruction. The interpreter, numeric charts and exact-value inspector retain their actual current state while the teaching scene can display its before-state.

The pure flow plan uses only the verified scene descriptor. It does not evaluate an expression or infer calculation order from the read list. It keeps original expression text, because collected input values are not an ordered explanation of operator precedence. It restores changed/departing memory and new output for the beginning of the replay, then commits the exact after-state at final arrival. Reading uses copies; it does not empty the source. New function inputs are distinct from the answer that will return later.

Slow playback defaults to one instruction every four seconds, with a two-second option and the existing faster speeds. Saved programs retain their valid selected pace, including older saved speeds. Selecting a replay control pauses execution without advancing or rerunning the program. The enlarged scene remains paired with source code and the full-screen-width Expand view option.

The flow-model suite contains 41 checks, and the combined scene/flow model suite passes 92 checks. A separate review checked 480 executed instructions across 24 templates and 12 boundary fixtures against actual interpreter snapshots. This includes repeated reads, same-value stores, string/array indexing, replacing an array with one of its values, computed arguments and returns to an outer scope. Reduced motion permits static phase inspection; failures, Back, loop jumps, initial state and completion do not invent a successful value flow. 

Integrated verification passes **268 Node test entries**, including 41 flow-plan tests, 19 scene layout/timing tests and nine renderer lifecycle/framing tests. The renderer harness uses actual Three.js geometry, materials and camera projections with controlled browser drawing and animation time. It verifies unchanged interpreter state during replay, before-values at 99% and after-values at 100%, separate output slots, function ownership, cancellation and resource cleanup. Projection tests keep all four input cards inside narrow 360×500 and 290×500 canvases; the single output label remains fully contained and at least 140px wide. The catalog audit passes all 11 app routes and 212 local resource/link references.

Browser review verified readable source copies and moving answers, distinct numbered stations, the completed result arriving last, replay and keyboard scrubbing, and both themes. Desktop, 320×740, 390×844, 768×1024 and 844×390 layouts have no horizontal page overflow; phase controls are at least 44px high. The phone camera follows the active stage, using a wider view for multiple inputs and closer views for the calculation and destination. The normal desktop viewport and dark theme were restored, with the times-table example ready at its multiplier update. No application console errors or warnings were recorded. These checks use emulated browser viewports and automated tests, not physical-device testing or a learner study.

### Connected program map — 21 October 2026

The workbench now opens in a separate 3D control-flow map. Main and every declared function have their own areas, ordered by the call tree. Curved repeat paths connect loop ends to their conditions; Yes/No paths distinguish continuing from leaving. Call paths enter a function, and dashed return paths reconnect it to the correct waiting caller. Functions may call helpers or themselves; recursive invocations retain distinct call-stack IDs. MiniScript still defines functions at the top level, not inside other function definitions.

The map is compiled from the same source and keyed by scope plus instruction counter, so repeated source lines do not merge different instructions. Live paths are checked against an isolated replay of the actual interpreter step. Errors and Back do not invent a successful forward traversal. Returns follow the caller's control location even when the returned value is stored in a global variable. All possible paths remain visible; only the actual current traversal becomes bright orange. Synthetic completion and implicit return blocks are labelled separately from source instructions.

Small desktop programs start with the whole map. Larger programs and narrow screens follow the active function, with a closer camera window on phones. Call and return transitions keep both participating areas visible. Orbit, pan, zoom, top view, function selection and an exact-code inspector do not run code; selecting a block pauses and marks its source line. Manual camera and scope choices persist. An accessible text map lists every block and connection, including without WebGL. Hidden scenes stop rendering and release resources when disposed. The existing value-flow replay and memory cards share the same execution state.

The new **A snack-packing team** template combines one loop with main → pack → pairs calls and both return journeys. Its line notes explain the role of each instruction; output is Bag 1: 3, Bag 2: 5, Bag 3: 7, then All bags ready. The library now contains **25 templates**.

Integrated verification passes **312 Node test entries**, including 24 program-map model tests and 16 map renderer/layout tests. Independent review checked 42 programs and 577 forward/rewind cases, including nested loops, recursion, early returns and failures. A separate visibility audit checked 493 traversed edges across all 25 templates, including 48 call/return edges. The catalog audit verifies all 11 app routes and 215 local resource/link references.

Browser review verified the helper-call stack, both return journeys, loop repeat, source inspection without execution, Back, and switching among map/value/card views. Both themes were inspected; 320×740, 390×844, 768×1024 and 844×390 layouts had no horizontal page overflow. Phone camera framing and 44px map controls were checked. No browser errors or warnings were reported. The desktop dark preview is left on the snack team's nested call. These are automated and emulated viewport checks, not physical-device tests or a learner study.

### Full audit — 21 October 2026 (evening)

The language is unchanged; only diagnostics and controller behaviour changed.

- **Near-miss statements now get targeted messages.** `} else {`, `else if`, a stray `else {`, `if(`/`while(` without a space, an `if`/`while` line without `{`, `x += 1`, `x++`, `a[0] = 5`, a bare `g()` call and `function`/`def` headers each explain the supported form. Anything else keeps the original "Unknown statement" text.
- **Missing closing brace points at the open block.** The error line is now the line that opened the unclosed `if`, `while`, `else` or `fn` block, not the last line of the file.
- **Pause points stop once per visit.** Loop back-jumps, `if`/`else` skip-jumps and the final halt reuse a source line number. They no longer trigger a second pause on a `while`/`if` header or on the last line. "Next" reads "finish the program" before the halt.
- **Run to end is much faster.** It works in ~24 ms slices and repaints at most every 200 ms. The interpreter also caches its serialized state between instructions, and rewind history keeps those JSON strings instead of a re-parsed copy. Semantics and limits are unchanged: the 500-snapshot / 2-million-character rewind bound is still measured on the same strings. A runaway loop now reaches the 3,000-instruction guard in about 2 s in headless software-rendered Chromium, down from about 18 s.
- **Phone pace control.** On screens 480 px wide or less, the visible "Pace" label is hidden but stays available to screen readers, so the pace option text is no longer cut off.
- **Light-theme contrast.** The headline `em` uses the theme's accessible accent text colour (`#a64a00` on light) instead of raw `#ff8a1f` (2.06:1).

Verification: 322 Node test entries pass. New coverage is in `tests/statement-hints.test.cjs` and `tests/execution-cache.test.cjs` (snapshot isolation, cache invalidation, step/rewind parity), plus three controller tests in `tests/app.test.cjs`. axe reports no violations at 390×844 or 1440×900 in either theme, and there is no horizontal overflow.
