plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


f5ce2ba — Peter John 2026-11-03T21:20:05+05:30
docs(plans): state/view parity review plan; pure-scene-enums plan notes
docs/plans/2026-11-03-pure-scene-enums.review.md CHANGED
@@ -21,6 +21,8 @@ The mutable `std/game/Scene`, `game_scene.plum`, `scene.plum` and the trait exam
21
21
  - `own record Node` holds the fields every node has: id, position, size, origin, scale, rotation, colour, visible, touchable and actions, plus `kind: Kind`.
22
22
  - `Kind` is an inline-variant enum: `Group(children: Slice[Node])`, `Sprite(texture region)`, `Label(text, size)`, `Button(text, textures, pressed, over)` and `Custom(actor: Actor)`.
23
23
  - `n.x` stays a plain field read, and there's no 14-field header to repeat.
24
+ - Added mid-run (user): the position, size, origin, scale and rotation fields come from an embedded `record Transform`, which also carries `localMatrix()` and the box hit test.
25
+ - Trees and child lists are written with `Slice(a, b, c)` / `List(a, b)` instead of push chains. Plum has no `[a, b]` literal.
24
26
  - Narrowing to a std kind is a `match` on `n.kind`.
25
27
  - **The game's kinds are records behind a behaviour-only trait** (Rust's `Box<dyn Actor>`).
26
28
  - `trait Actor` in std has no fields, only two methods:
docs/plans/2026-11-03-state-view-parity.review.md ADDED
@@ -0,0 +1,120 @@
1
+ # Functional state/view at feature parity with PureScene: review plan
2
+
3
+ **Builds on:** master after `2026-11-03-pure-scene-enums` (its T1–T6; T7 dropped).
4
+ **Goal (user, 2026-11-03):** the most functional approach, where a frame is a recursive value built from state and walked recursively, with the same features as PureScene. Performance is measured and reported, not chased: matching the mutable scene isn't a goal ("otherwise I would just stick with mutate").
5
+
6
+ ## Summary
7
+ `game_state_view` + `std/game/Picture` is already the functional style:
8
+ - `State` holds plain values and cons lists;
9
+ - `step(state, msg)` returns a new state;
10
+ - `view(state)` builds a recursive `SceneNode` tree every frame;
11
+ - `Picture.draw` / `bounds` / `hitPath` recurse over it.
12
+
13
+ Nothing is mutated. What it lacks compared with PureScene:
14
+ - timed actions;
15
+ - buttons with press/enter/exit/release/click events;
16
+ - pointer capture and event routing by id.
17
+
18
+ This plan adds those as pure functions over values, ports the Orb Run state/view game to use them, and benchmarks it. It answers whether PureScene is still needed.
19
+
20
+ ## Folders (user, 2026-11-03)
21
+ The three scene styles currently share `plum-std/game/`, which is confusing. Each style gets its own folder; the shared basics stay where they are.
22
+
23
+ | Folder | Contents | Import |
24
+ |---|---|---|
25
+ | `plum-std/game/` | shared: `Game` (host API), `Vec2`, `Color`, `Affine`, and the mutable `Scene` | `std/game/Scene` (unchanged) |
26
+ | `plum-std/game/pure/` | PureScene, SceneTree, SceneInput, Actions | `std/game/pure/PureScene`, ... |
27
+ | `plum-std/game/functional/` | Picture, plus the new Anim and Ui | `std/game/functional/Picture`, ... |
28
+ | `plum-examples/games/pure/` | `game_pure_scene.plum` | |
29
+ | `plum-examples/games/functional/` | `game_state_view.plum` | |
30
+
31
+ The mutable examples (`game.plum`, `game_scene.plum`, `scene.plum`, `trait_scene.plum`) stay at the top of `plum-examples`. Imports in std, examples, web-test fixtures, host tests, web checks and the README are updated in the same part, and an import of an old path is a compile error. This is done first, as one mechanical part, so nothing else moves twice.
32
+
33
+ ## The design
34
+ - **Animations are functions of time, not stepped state.**
35
+ - New `std/game/Anim`: `enum Anim = MoveBy(dx, dy, secs) | RotateBy(radians, secs) | ScaleTo(sx, sy, secs) | Delay(secs) | Seq(steps: List[Anim]) | Par(steps: List[Anim]) | Forever(anim: Anim)`.
36
+ - `Anim.at(anim, t) -> Pose` gives the pose offset `t` seconds after the start; `Anim.length(anim)` gives the duration, which is infinite for `Forever`.
37
+ - The state stores only when an animation started, so there's nothing to step, compact or copy, and no drift: `Forever` is `t` modulo the lap length, and `Seq` picks its segment by time.
38
+ - The view applies the pose as a `Transform` on the node.
39
+ - **UI is a pure function of pointer, state and picture.**
40
+ - `SceneNode` gains `Tagged(id: Int, child: SceneNode)`, and `Picture.hitId(node, x, y) -> Int?` returns the topmost tagged node under the point.
41
+ - New `std/game/Ui` holds `record UiState(pointerX, pointerY, down: Bool, hot: Int?, captured: Int?)`. `Ui.step(ui, input, hit: Int?)` returns the new `UiState` and a `List[UiEvent]`.
42
+ - It uses PureScene's rules:
43
+ - Press on down;
44
+ - Enter/Exit on move;
45
+ - Release on up;
46
+ - Click only on up over the node that captured the press;
47
+ - capture is dropped when the captured id is no longer in the picture.
48
+ - Button visuals come from the UI state: `Ui.pressed(ui, id)`, `Ui.hovered(ui, id)`. `Ui.button(id, label, rect, ui)` builds the button's `SceneNode`.
49
+ - Events are `UiEvent(id, kind)`, as in PureScene, and the game handles them in `step`.
50
+ - **The game shell stays thin.** `update` reads input into a `Msg`, then calls `step`, then `Ui.step` against `view(state)`, then `step` again for each `UiEvent`. Sound remains the only side effect.
51
+
52
+ ## What you will get
53
+ - `std/game/Anim` and `std/game/Ui` with tests named after PureScene's. Button: press needs press-and-over, release elsewhere gives no click, drag off and back gives one click. Capture: stale capture dropped. Actions: zero-length, forever with no drift, sequence handover, parallel.
54
+ - `Picture` with `Tagged` and `hitId`. Existing Picture tests unchanged.
55
+ - `game_state_view.plum` using them:
56
+ - the restart button through `Ui` instead of the hand-written `onRestart` hit test;
57
+ - a title/game-over animation and the ship's hit flash through `Anim`;
58
+ - game rules and existing tests unchanged in value.
59
+ - `stress_state_view` fixture with tagged nodes, a few buttons and running animations, so the benchmark exercises the new parts.
60
+ - A benchmark table (headed Chromium, same session): mutable scene, PureScene, state/view before and after this plan. The plan reports the numbers. The only bar is that state/view keeps every frame well inside the 120 Hz budget (p95 under 4 ms on the M3 Pro).
61
+ - README: a "Functional scenes (state/view)" section showing the `State → view → draw` loop with Anim and Ui.
62
+
63
+ ## What will be built
64
+ | Part | What it does | Size |
65
+ |---|---|---|
66
+ | Folders | move pure and functional modules and their example games into their folders; update every import and test path | S |
67
+ | Anim | `enum Anim`, `at`, `length`, poses; tests for zero-length, sequences, parallel and forever without drift | M |
68
+ | Picture tags | `Tagged(id, child)`, `hitId`, draw/bounds pass-through; tests | S |
69
+ | Ui | `UiState`, `Ui.step`, events, capture, `pressed`/`hovered`, `button`; PureScene-equivalent tests | M |
70
+ | Orb Run state/view | restart via Ui, Anim for the title and hit flash; tests unchanged plus Ui/Anim cases | M |
71
+ | Stress fixture | tags, buttons, animations in `stress_state_view.plum` | S |
72
+ | Benchmark + README | headed table, "Functional scenes" section | S |
73
+
74
+ ## How it fits together
75
+ ```mermaid
76
+ flowchart LR
77
+ I["host input"] --> M["Msg"] --> S["step(State, Msg) -> State"]
78
+ S --> V["view(State) -> SceneNode (recursive, Tagged ids)"]
79
+ V --> H["Picture.hitId"] --> U["Ui.step(UiState, input, hit) -> UiState + List[UiEvent]"]
80
+ U --> S
81
+ V --> D["Picture.draw"]
82
+ A["Anim.at(anim, now - start) -> Pose"] --> V
83
+ ```
84
+
85
+ ## Decisions you can change
86
+ - **Animation model.**
87
+ - Default: pure functions of time, with no stepping state.
88
+ - Alternative: PureScene-style stepped actions kept in `State`, more like libGDX but more state to carry.
89
+ - **Hit ids.**
90
+ - Default: a `Tagged(id, child)` wrapper node.
91
+ - Alternative: an `id` field on every `SceneNode` variant.
92
+ - **PureScene's future.**
93
+ - Default: decided after the benchmark and the supercompiler.
94
+ - Alternative: retire it right after this plan.
95
+
96
+ ## Risks
97
+ - **R1: allocation.** `view` rebuilds the tree every frame, and the UI adds a hit test per input. Measured and reported; the supercompiler's view-into-draw fusion is the later lever.
98
+ - **R2: hit test against the view.** `Ui.step` needs `view(state)` for `hitId`. Building it twice per frame (once for input, once for draw) is allowed, measured, and may be shared later.
99
+ - **R4: nested std paths.** No std module lives three levels deep yet (`std/game/pure/X`). If the loader, LSP or `plum game` asset/bundle code assumes two levels, the Folders part fixes that in the same part, with a test.
100
+ - **R3: compiler gaps.** Recursive types and generic `List` code have hit gaps before. Each gap is reported as an incident and fixed in the compiler, not worked around.
101
+
102
+ ## Out of scope
103
+ - Removing PureScene, SceneTree, SceneInput or Actions (a later decision).
104
+ - Fixed timestep and replays (separate queued plan).
105
+ - The supercompiler.
106
+
107
+ ## How we will know it works
108
+ - Anim, Ui and Picture tests pass, including PureScene-equivalent button and capture cases.
109
+ - `game_state_view` tests are unchanged in value. New tests cover a restart click through Ui and the hit-flash pose at fixed times.
110
+ - Host parity and browser checks pass for `game_state_view`.
111
+ - Full suites stay green.
112
+ - Benchmark table reported; p95 under 4 ms for state/view.
113
+
114
+ ## Rough time and cost
115
+ About 6 parts:
116
+ - in parallel first: Anim, Picture tags and Ui;
117
+ - then the game and the fixture;
118
+ - then the benchmark and README.
119
+
120
+ Rough agent time is 45–70 minutes, plus one headed benchmark run.