1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
# Fieldwork ยท Electrophysics
An interactive 3D learning lab built with TypeScript, Vite, Three.js, and KaTeX.
`PLAN.md` controls the subject scope. The only implemented subject is **Electric flux and charge**.
## Run locally
Use Node 22 (at least 22.12.0), as specified in `.nvmrc`.
```sh
npm install
npm run dev
```
Open the local URL printed by Vite. `npm run build` type-checks and builds the app into `dist/`; `npm run preview` serves that build. `npm run typecheck` runs TypeScript separately.
No test scripts or test files were created for this implementation. The pre-existing test commands and dependencies in `package.json` are retained, but there is no test suite in the current working tree.
## The experiment
- Change a point charge from โ5 to +5 nC and move it along the x-axis.
- Choose a sphere or cube and change its radius or half side length.
- Compare enclosed charge, surface area, and exact net flux.
- Try positive, negative, and external-charge presets.
- Toggle the surface, field lines, and outward normals.
- Drag to orbit; scroll to zoom. Focus the canvas and use arrow keys to orbit, `+`/`โ` to zoom, and `Home` to reset. All other controls are native keyboard-operable inputs.
The model assumes a stationary ideal point charge in vacuum. The imaginary Gaussian surface is centered at the origin. Geometry uses metres, physics uses SI units, and the interface presents charge in nanocoulombs. A nonzero charge exactly on the surface produces an undefined enclosed-charge/flux result because its field is singular there. The visible marker's radius does not change the point-charge model. Sampled lines and fixed-size arrows illustrate field direction; total flux is calculated analytically, never by counting those lines.
Reference: `docs/theory/3Electric flux and charge.pdf`, sections 3.2โ3.4. Lesson copy and graphics are original, based on these definitions.
## Add another 3D model
1. Enable its subject in `PLAN.md` first.
2. Create `src/models/<model-id>/` with separate physics, state, scene, and view modules.
3. Implement `ModelDefinition` from `src/core/model.ts`. `mount(host, { i18n })` renders into its host and returns a `ModelInstance` whose `dispose()` releases listeners, GPU resources, and observers.
4. Add its title, controls, explanations, accessibility text, and messages to `src/i18n/en.ts`.
5. Register a lazy-loading definition in `src/models/registry.ts`. The current shell mounts its first entry. Add model selection to the shell when a second enabled model is implemented; each model's physics and scene remain independent.
`src/models/electric-flux/physics.ts` has no dependency on DOM, Three.js, or translations. Rendering consumes physical field directions; it does not own the equations or measurement state. Scenes render only when controls, state, or viewport size changes.
## Add a language
English (`en`) is the default and fallback. User-facing copy is in `src/i18n/en.ts`, accessed through stable typed keys. Messages accept named parameters and numbers use `Intl.NumberFormat`.
To support another language, add its catalog and register its BCP 47 locale in `src/i18n/index.ts`. Pass that locale to `createI18n` in the shell. Missing messages fall back to English. No physics or model components need rewriting. Add a language selector only once another language has been requested and supplied.
## Layout
```text
src/core/ Shared model lifecycle contract
src/i18n/ English catalog, locale registry, formatting
src/models/registry.ts Enabled, lazy-loaded models
src/models/electric-flux/ Pure physics, state, Three.js scene, lesson view
src/ui/ Shared interface graphics
src/main.ts Application shell
src/style.css Responsive presentation
```
|