Skip to content

PhET Simulation Pilot

Philip Pavlik edited this page Nov 12, 2026 · 3 revisions

PhET simulation pilot

This is a local feasibility pilot, not a deployed service or catalog-wide compatibility claim. Density 1.2.9, Pendulum Lab 1.0.34, and Circuit Construction Kit: DC 1.5.1 compile from pinned public source with one shared collector. The selected manifests match the latest publicly retained numeric release-branch tips checked on November 9, 2026. They do not represent current private PhET releases.

Author a simulation node

The SPARC authoring catalog includes an atomic simulation node. It requires an accessible label and a simulation object containing an operator-approved buildId and one to 32 declarative bindings:

In the visual editor, select Simulation from the palette, enter its label, choose an approved build, and edit Observation bindings (JSON). Changes apply when the field loses focus; invalid changes show an inline error and are not applied. The starter address is an explicit placeholder and must be replaced with a validated catalog address before launch. Authoring preview displays the label without starting a simulation or collecting telemetry.

{
  "id": "simulation",
  "nodeType": "atomic",
  "atomType": "simulation",
  "label": "Density: mass and volume",
  "simulation": {
    "buildId": "replace-with-the-validated-pinned-build-id",
    "bindings": [
      {
        "name": "mass",
        "label": "Block mass",
        "unit": "kg",
        "address": { "path": "screens.1.model.blockA.massProperty" },
        "projection": "value"
      }
    ]
  }
}

Paths are first-discovery addresses, not continuously updated array positions. Each address has exactly one structural path or supplied tandem name. Validate it against the pinned build's catalog. Object identity is a session-local ID; array positions are descriptive paths and must not identify dynamic components. Missing, ambiguous, unsupported, or incomplete required evidence stops the attempt. Required values must have observed notification coverage; initial-only fields are rejected.

The supported projections are value for scalar properties, count for collection size, and any-nonzero for an explicitly named numeric memberProperty of collection members. any-nonzero uses an absolute numerical tolerance of 1e-8. The circuit activity uses currentProperty. These are declarative projections over the common catalog and snapshots; there is no circuit-specific collector.

Bindings become slots in an interface-event fact with eventType: "simulation-observation". Author conditions and effects in the existing productionRules DSL. The host evaluates settled frame checkpoints in order after ordered browser receipt, independently of research storage. It does not turn observations into answer submissions or KC/model updates. Those require explicitly authored effects. This pilot uses static authored rules and does not support an AutoTutor/LLM-driven simulation policy.

Guard completed tasks with persistent SPARC state. The example activities set simulation.completed and require it before the learner selects Done. Repeated observations cannot repeatedly award task completion. Completed work and prior instructional history survive reload; the physical simulation state does not.

Local builds and activities

The application repository owns packages/phet-monitor/ and tools/phet-pilot/. releases.json pins the original dependency manifests and supplemental locale metadata. Acquisition and builds reject unexpected source edits. Density and CCK have the explicitly approved supportedBrands metadata addition for adapted-from-phet; Pendulum's baseline is unmodified. Shared changes are confined to Axon, Joist, Scenery, Tandem, and the Chipper preload list.

After building all three monitored artifacts with the current shared sources:

node tools/phet-pilot/generate-catalogs.mjs
node tools/phet-pilot/create-activities.mjs 'C:/dev/mofacts_config/SPARC PhET Pilot'
node tools/phet-pilot/verify-activities.cjs 'C:/dev/mofacts_config/SPARC PhET Pilot'
node tools/phet-pilot/serve-pilot.mjs

The canonical lesson directory contains three TDF/stimulus pairs. The upload package is C:/dev/mofacts_config/SPARC PhET Pilot.zip. The generator binds each activity to the current verified build identity; regenerate and update the local lessons when shared sources change.

The simulation server binds only 127.0.0.1:3212, verifies artifact hashes, and serves only the three approved HTML artifacts and their hash-verified descriptive catalogs. It writes an ignored tmp/phet-pilot/settings-fragment.json containing public.simulationBuilds. Merge that public catalog into an isolated ignored local settings file and restart the canonical Docker watcher at http://localhost:3200 through deploy/hotfix-local.ps1. Do not edit production settings. The pilot bridge permits only that host origin. No second MoFaCTS application server is used.

Import the ZIP under the intended author/admin account through Content → Upload MoFaCTS Package, then launch the new activities from Practice. They demonstrate:

Activity Instructional condition
Density Increase wood volume above 5 L while mass/volume remains 400 kg/m³
Pendulum Lab Change length to at least 0.75 m, then observe left–right–left motion
Circuit Construction Kit: DC Create and remove components, then form a circuit with nonzero current

The pendulum task detects angular crossings from observed property changes at settled checkpoints; it is not an exact period estimate. Separate browser probes passed representative native pointer/keyboard controls, reset, and screen switching for all three simulations, with supported-state reconstruction checked at each stage. The local Pendulum URL includes its documented ?accessibility flag; the current ten-minute comparison uses that same flag in both baseline and monitored runs. CCK keyboard deletion in the embedded test uses the native focusable wire control; a canvas-only selection with BODY focus did not respond to Delete in this browser. Closed-circuit setup in the reproducible host test uses public model APIs. These probes do not constitute a complete accessibility audit.

Meaningful activity in ordinary history

The shared event monitor feeds stimulus-defined bindings and production rules. A rule may emit one history-event: expression-valued selection and action, input as an expression or a template using the existing single-brace variable syntax, optional expression-valued outcome (default unknown), optional expression-valued values, and optional endsAttempt: true. For example:

{
  "type": "history-event",
  "selection": { "type": "literal", "value": "Wood block" },
  "action": { "type": "literal", "value": "Adjust volume" },
  "input": "Volume {previous_volume} → {volume} m³"
}

Bindings may include label and unit (up to 120 characters each). Both propagate with selected values into history; lesson authors must use the actual model units. Density's model volume is in m³ although its slider displays liters. Shared framework-declared property links associate the completed control interaction with the scientific property; no simulation-specific collector or guessed conversion is used.

Shared facts include selected binding names, previous_<name>, simulationEvent, interactionBinding, and changeCount. Event names are observation, interaction-completed, interaction-cancelled, reset, collection-added and collection-removed. Collection events retain added/removed member identities and transition counts even between checkpoints. Reset produces one explicit activity and suppresses reset-induced removal rows. Detailed raw notifications are preserved separately. A property or topology change alone is an observation; only a completed interaction with trusted framework input provenance is labeled input. Historical Reset All callbacks without such provenance remain observations.

Use normal Download for meaningful Selection–Action–Input–Outcome history. The existing columns retain their positions and a bounded, lossless JSON CF (Simulation Activity) column carries version, build/attempt/source-event identities, origin, selected values, labels/units, and supporting sequence references. Authored simulation activity and lifecycle rows are exported. Internal simulation state-transition and rule-trace rows remain available for replay and are omitted from ordinary export; other units and existing rows retain their behavior. Raw catalogs and detailed notifications never become thousands of ordinary activity rows. New history effects do not classify answers, award task credit or update KCs; those require separate authored effects.

Information Owner
Build identity, supported descriptive addresses Hash-verified build catalog referenced by approved settings
Selected variables, labels/units, event/task conditions, hints and KC effects Stimulus production rules and simulation configuration
Actual initial values, dynamic IDs Attempt metadata and detailed research trace
Start, meaningful changes, unsuccessful checks, hints, completion, interruption and failure Ordinary canonical history
Internal notifications, catalogs and reconstruction state Separate indexed research collection

The catalog is a credential-free, same-simulation-origin asset with SHA-256 in public.simulationBuilds[].catalog: { "url": "http://127.0.0.1:3212/density.catalog.json", "sha256": "<64 lowercase hexadecimal characters>" }. Generate it after a matching successful monitor probe with node tools/phet-pilot/generate-catalogs.mjs. The host verifies its hash, build/artifact/protocol identity, and required addresses before enabling Start. It contains no attempt values or runtime object identities.

Lifecycle writes reuse authenticated canonical history ingestion and deterministic retry IDs. Interrupted attempts keep their original times and completed tasks; re-entry requires explicit Start. Host actions after reload may refer to last recorded evidence, marked last-recorded-state-after-reload, and do not claim restored physics. The pilot associates general host buttons only with an unambiguous single simulation node. Multiple simulations per page need explicit authored association before this convenience is extended.

Use verify-host.mjs <fresh-fixture-ids.json> --hold-research for live feedback/persistence/reload checks, then verify-history.mjs <fixture-ids.json> for actual Download UI checks across own-history, owned-data and individual-lesson scopes. Verification uses synthetic fixtures and the canonical ignored test credentials; update user-facing demos in place under their original admin owner. Do not clear learner history to repeat a test. The uploader, host/history verifiers and ingestion benchmark share tools/phet-pilot/local-app.mjs for full headless Chromium, exact localhost origin validation and canonical ignored test credentials. Run its three isolated tests with node --test tools/phet-pilot/local-app.test.mjs; they never read real credentials. User-facing trials use Chrome. measure-ingestion.mjs <fixture-ids.json> records synthetic attempt/lifecycle rows with canonical session/unit context, without task credit; use test fixtures only. Build/reproduction and probe commands are maintained in the application tools/phet-pilot/README.md.

Trace integrity and research access

Protocol 3 separates reactive instruction from research persistence. Validated ordered batches update SPARC working memory and rules directly. Browser receipt acknowledgments release the child to deliver further events while an independent ordered writer saves research batches. The shared collector remains object-event based, with no model polling. Required bindings and sequence/coverage checks still apply before instructional effects.

The host retains accepted batches in a bounded 32 MiB queue until storage completes. Research failure or overflow stops further dependent instruction and marks the attempt incomplete. Decisions already made keep their attempt/sequence references and are not retroactively erased; their raw evidence can still be pending. Normal departure drains accepted writes before closing; abrupt browser loss can interrupt unsaved research. Restart preserves completed work and marks the previous attempt interrupted. This pilot does not provide a crash-proof offline outbox.

Use verify-host.mjs <activity-ids.json> --hold-research to hold the first research write until actual instructional feedback appears, then release storage and verify normal persistence/reload. The failure and bounded-queue contracts are tested with synthetic streams.

The iframe receives a random attempt identity, protocol/build metadata, and acknowledgments. Authentication and learner identity remain in MoFaCTS. Messages require the exact origin, iframe source window, protocol version, build, and attempt. Traces are split into ordered chunks of at most 4,000 records and 1 MiB of UTF-8 JSON; record notifications schedule a flush in the next event-loop task. Idle streams have no recurring timer; outstanding batches have a one-second retry and a ten-second deadline. Identical retries share the pending transaction. Conflicting retries, sequence gaps, timeouts, queue overflow, individually oversized records, and unsupported required bindings stop dependent instruction.

simulation_telemetry is a server-only collection separate from scored history. It stores authenticated attempts and deduplicated ordered batches. Mongo transactions preserve the attempt cursor and batch atomically. Indexes support exact user/scope interruption and bounded per-attempt export. Reload interrupts the old attempt before enabling the explicit Start button. Host failures mark the attempt incomplete and retain a bounded reason such as host-queue-overflow or transport-timeout; ordinary departure records interrupted with left-node. Existing histories retain instructional decisions and their observationEvidence references.

Raw research has no broad reactive publication or ordinary Download entry. Meaningful authored activities use the normal history export described below. exportSimulationResearchTrace(attemptId, afterSequence) requires an administrator and returns attempt metadata, at most four batches, and a nextSequence cursor. Start at zero and pass the returned cursor until the batch list is empty. Attempt status and reason must accompany research interpretation. Raw traces must not be interpreted as complete physics replay or automatic correctness observations.

The existing administrator-authorized account deletion removes that user's research attempts and batches through the telemetry owner. Deleting lesson content or assets does not erase research traces. No account deletion was performed while implementing the pilot.

The shared collector uses object-level events after one initial model discovery. Property notifications, emitter events, and Axon observable-array API mutations are observed directly. Frame callbacks settle already-captured records and affected cached identity edges; they do not inspect model values. No model-step hook, polling mode, full-model rescan, or full-frame reference collector remains.

Plain fields, ordinary arrays/maps/sets, typed-array physics buffers, and vector/matrix direct writes are initial-only, unsupported for continuous observation. Objects explicitly carried by events may receive labeled event-point snapshots. Getters, closures, and rendering/browser objects remain unsupported. Direct backing-array or defineProperty writes bypassing Axon are unsupported. The trace contains object and field-level coverage inventories; required bindings cannot use initial-only data.

Startup capture begins before the first model factory. Early primitive notification values and order are retained until ownership is known; pre-discovery mutable references resolve later and cannot establish their earlier field values. Earlier module-loading activity is outside coverage. Dynamic identity uses IDs and collection references, while structural paths describe first discovery. Event-driven trial deletion releases affected disposed/detached subgraphs without model rescans. Silent ordinary-array membership changes cannot establish detachment.

Verification and release boundary

The previous protocol-2 qualification recorded passing local functional/timing checks and one full Meteor course-authorization timeout. Its results are historical; they do not qualify the current protocol-3 builds.

Build logs, source/license inventories, artifact hashes, compressed model traces, coverage inventories, screenshots, and measurements are retained under the ignored application tmp/phet-pilot/ directory. The source and build instructions remain reviewable in the repository. Upstream notices and license files are retained. A distribution/asset-license review is not established by the inventory alone.

Research-only coalescing starts a 100 ms window for each pending storage group. Contiguous receipts are combined into bounded storage batches without changing record sequences. Further writes follow completion of the preceding write and receive their own coalescing window, limiting sustained requests to at most ten per second. No timer runs on an idle queue. This storage scheduling never gates SPARC observation delivery.

The shared monitor now uses an explicit version-1 registration contract for properties, emitters and observable collections. Shared framework constructors supply kind, exact notification source, public value reader and collection child handles. Rendering/instrumentation exclusions use registered constructor identities. Private-field lookalikes never gain observed coverage. The declaration in packages/phet-monitor/registration-contract.d.ts is identical across the three adapted builds; release manifests record its hash. Framework adapters preserve startup capture, collection identity, disposal and the independent reactive SPARC/research paths. No simulation model is patched and no polling mode is introduced.

Recorded event history

Each attempt has an ordered JSON trace: object catalogs and initial state; property notifications and emitter arguments; event-triggered object snapshots; collection membership changes; disposal/detachment; settled checkpoints and monitoring errors. Records carry sequence, timestamp, frame, order and typed payload. Object IDs are session-local; catalogs supply structural paths and any Tandem address. A sample record is an event-triggered snapshot, never periodic model sampling. Silent mutable fields remain explicitly unsupported.

Research batches live in the separate indexed simulation_telemetry collection. SPARC history retains instructional decisions and their attempt/sequence evidence references. The current short launch-probe recordings contain 18,143 records for Density, 522 for Pendulum and 1,218 for Circuit, including catalogs and initial state. Their complete compressed JSON files remain under each simulation's ignored monitored/monitor-trace.json.gz. Live SPARC verification also confirmed actual database ingestion. The ten-minute benchmark files retain frame timings, action checks and cached inventory counts.

Current protocol-3 verification (2026-11-11 and 12 local)

The shared event collector passes supported-state reconstruction, startup ordering, native pointer/keyboard/reset/screen-switch checks, and resource cleanup in all three builds. One thousand idle frame callbacks inspect zero objects in each pilot. A 100-cycle CCK create/reset probe retained none of the 200 tested component/property WeakRefs after forced garbage collection; this is not a complete heap audit.

Simulation Current build Baseline p95 interval Monitored p95 interval Additional p95 time Scripted ordinary activity rows
Density density-1.2.9-cce0fa6475e2474e 9.0 ms 9.1 ms 1.1% 9
Pendulum Lab pendulum-lab-1.0.34-a11bcc23b8d1af40 8.6 ms 8.7 ms 1.2% 12
CCK DC circuit-construction-kit-dc-1.5.1-4345ad43e84da17e 8.7 ms 8.8 ms 1.1% 16

Each baseline and monitored session ran ten minutes, sequentially on the same machine in Chromium 148.0.7778.96. All six validity checks passed with no page errors; every monitored stream remained complete. All three satisfy the 10% p95 target. Maximum gaps were 17.6/354.8 ms for Density, 15.1/15.2 ms for Pendulum and 90.9/300.7 ms for Circuit (baseline/monitored). These are frame intervals from one pair per simulation, not direct CPU-cost measurements or worst-case responsiveness guarantees. Cached frame-ended inventory measurements precede collector settlement; settled active counts were 6,193/245/593 at the ends of the three monitored runs. Circuit's workload alternated between 388 after reset and a conducting circuit.

Actual SPARC feedback appeared while the first research write was held, then supporting observations were persisted after release. The iframe survived feedback updates. Reload preserved completed tasks and required explicit physical restart. The ordinary Download UI passed own-history, owned-data and individual-lesson exports with exact matching activity fields, no internal simulation rows and no duplicate activity. The scripted rows include unsuccessful checks, repeated hints, explicit reset, completed adjustments, component transitions, interruption and completion. These counts describe the scripted interactions, not a fixed row count for every learner.

Local checks passed 56 monitor/history/host/storage contracts plus 19 Download transport/cancellation tests, Linux typecheck/lint and schema generation. The earlier full Meteor snapshot 93373fa859b5225e9a49c76273fa06b789321414 passed 1,018 server and 1,249 client tests before the final export/contract refinements. A fresh final-source run on November 12, 2026, passed on private detached snapshot b1c0335598e71219d55c58dee4a5e00c85144fb3, containing the published PhET work and local pilot-tool cleanup: 1,019 server and 1,249 client tests passed, zero failures or hook failures, 15/seven pending. Retained source before/after matched, pre-existing containers were unchanged, and all worker-owned containers, network and cache volume were removed. The local receipt is tmp/phet-pilot/cleanup-integration-20261112-results/summary.json; raw evidence remains under /var/tmp/mofacts-integration.AObNShULZh93/results on Ubuntu-MoFaCTS. This closes the final-source integration rerun. The earlier failed protocol-2 result remains historical below. These results establish local test evidence; publishing the source does not establish deployment qualification.

The browser checks use ordinary owned lessons. Course-assignment launch-context propagation into server-written lifecycle rows is not qualified by this local pilot. CCK's pinned build exposes a blank component label after Reset All; the verifier uses its unique declared native component-edit focus target for Delete. Complete accessibility and physical-state replay are not established.

Current local evidence is in ignored tmp/phet-pilot/acceptance-summary.json, history-acceptance.json, history-meteor-first.json, and per-build probe/performance files. Matching descriptive catalogs are archived with their artifacts and modified shared source. The canonical six-file lesson ZIP and three existing main-admin lessons were updated; lesson IDs, ownership and prior progress were preserved.

Historical protocol-2 comparisons and Meteor run (2026-11-11)

Previous protocol-2 registration verification passed on its three build identities: 43 contracts, full application typecheck/lint, reconstruction and startup checks, zero inspections over 1,000 idle frame callbacks, representative native controls/reset/screen switching, and a 100-cycle CCK retention probe with zero retained references among 200 tested component/property references. Real SPARC feedback preceded held research writes, eventual storage reached the supporting observations, and reload preserved completed tasks. Exact source/build hashes and results are in ignored tmp/phet-pilot/registration-verification.json. No deployment was performed. Feedback timing is a diagnostic, not a monitor correctness requirement.

All six ten-minute sessions completed on November 11, 2026, in Chromium 148.0.7778.96 using the same Intel Arc/ANGLE renderer. Each baseline/monitored pair used the same scripted action policy, viewport and launch configuration. Actual action counts were 299/298 for Density, 299/299 for Pendulum and 297/294 for Circuit; action execution time contributes to the two-second loop cadence. All runs passed visibility, elapsed-duration and maximum-gap validity checks, with no page errors. All monitored streams remained complete. The collector, bridge and registration declaration hashes were identical across all three builds.

Simulation Baseline p95 frame interval Monitored p95 frame interval Additional p95 time 10% target
Density 9.0 ms 9.0 ms 0% at recorded precision Passed
Pendulum Lab 8.6 ms 8.7 ms 1.2% Passed
Circuit Construction Kit: DC 8.7 ms 8.8 ms 1.1% Passed

This is one local sequential pair per simulation. The comparison isolates collector/rendering with an event-driven local drain; SPARC transport and asynchronous database storage are tested separately. The benchmark installs no per-notification profiling wrappers. Long frames remain: monitored maximum gaps were 447 ms for Density, 18 ms for Pendulum and 317 ms for Circuit (baseline maxima: 188, 13 and 65 ms). Passing the p95 target does not establish worst-case responsiveness or zero CPU cost. Cached inventories showed bounded transient variation, returning to 6,193/245 objects for Density/Pendulum at settlement; Circuit returned to 388 after reset. Frame-ended inventory samples precede collector settlement and include transient objects.

One explicitly authorized full Meteor run used clean committed source 19334b49 (including PhET commit 64cc3edb) in the isolated 6 GiB/two-CPU Linux worker. It exited 1: 1,009 server cases passed, one timed out, 15 were pending; 1,249 client cases passed, zero failed, seven were pending; zero hook failures. The failing case was course method authorization / denies assignment edits by teachers who do not own the course, exceeding its 2,000 ms timeout. Its cause is not established. Retained source before/after matched, cleanup preserved the pre-existing containers, and no success receipt is claimed. A retry requires fresh authorization under the repository guide.

tmp/phet-pilot/qualification-20261111.json joins that revision's performance/coverage evidence, the failed Meteor result and the existing recording inventory. event-history-example.json contains small examples extracted from the original short-probe traces. Previous timing files are preserved as performance-before-registration-<timestamp>.json; they do not qualify the current builds.

Clone this wiki locally