plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
2aabaea
— Peter John
2026-11-03T14:40:50+05:30
docs(plans): own record phase B review and machine plan
docs/plans/2026-11-02-own-record.review.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Phase B: `own record` and a state/view Orb Run: review plan
|
|
2
2
|
|
|
3
3
|
**Builds on:** phase A (pure scene graph, branch `pure-scene-values`); design D2 and section 6 of `docs/plans/2026-11-02-pure-scene-graph.design.md`; benchmark verdict `docs/plans/2026-11-02-pure-scene.benchmark.md`.
|
|
4
|
-
**First step after approval:** commit phase A on `pure-scene-values` (local commit only, no merge, no push), so phase B builds on a clean base.
|
|
4
|
+
**First step after approval:** commit phase A on `pure-scene-values` (local commit only, no merge, no push), so phase B builds on a clean base. *Done 2026-11-03: phase A is commit on top of master 7e14e71 (rebased cleanly).*
|
|
5
|
+
|
|
6
|
+
**Added after the rebase: migrate phase A to master's syntax** (a careful helper, before wave 1). Master now has header constructors, `fun init`, no field defaults, record embedding and required `override fun`. Phase A breaks on the last one (`Button.clone implements Actor.clone; mark it override`) and probably on field defaults. This step covers PureScene, SceneTree, SceneInput, game_pure_scene, struct_union_members and the stress fixture, with every phase A test passing again. Behaviour and API stay the same. Because master's `World` now embeds `Stage`, the "own value inside an ordinary record" decision also applies to embedded records: an embedded own record makes its host record own, rather than using the swap form.
|
|
5
7
|
|
|
6
8
|
## Summary
|
|
7
9
|
Phase A's pure scene graph is about 15% slower than the mutable one, and all of the gap is in update: every animated actor is cloned every frame. Phase B adds `own record` (and `own trait`), a kind of record that is never shared. The compiler checks that an owned value isn't used after it's handed on. Because nobody else can see an owned value, the compiler may update it in place:
|
docs/plans/2026-11-03-own-record.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Phase B: own record and state/view Orb Run
|
|
2
|
+
|
|
3
|
+
**Review plan:** docs/plans/2026-11-02-own-record.review.md
|
|
4
|
+
**Goal:** `own record`/`own trait`/`own` params with an ownership checker and in-place spread codegen; scene modules on own; a state/view Orb Run; five-row benchmark.
|
|
5
|
+
**Architecture:** syntax (grammar, parser, AST flags) → ownership pass in plum-checker (owned/borrowed/moved per binding) → codegen turns spread of an own source into field writes on it → std List helpers → PureScene/SceneTree/SceneInput on own. Separately: std/game/Picture (ML-style SceneNode + interpreter) and a state/view Orb Run.
|
|
6
|
+
**Tech stack:** Rust workspace (Plum compiler: plum-core, plum-checker, plum-wasm-codegen, tree-sitter-plum), Plum std, plum-game host tests, playwright-rs.
|
|
7
|
+
**Spec:** docs/plans/2026-11-02-own-record.review.md
|
|
8
|
+
**Detail:** contracts
|
|
9
|
+
**Target-min:** 45
|
|
10
|
+
**Ignore:** docs/plans/2026-11-02-pure-scene-graph.sketch.pfl, docs/plans/2026-11-02-pure-scene-graph.design.md
|
|
11
|
+
|
|
12
|
+
## Global constraints
|
|
13
|
+
- Work only in /Users/pyrossh/Code/plum-pure-scene-values (branch pure-scene-values). Never touch /Users/pyrossh/Code/plum.
|
|
14
|
+
- Test one Plum file: `touch plum-cli/src/main.rs && cargo build --quiet -p plum-cli && ./target/debug/plum test <file>`. Rust: `cargo test -p <crate> --test <name>`. Always `touch` an edited .rs before trusting a build.
|
|
15
|
+
- Master syntax: header constructors (`record Ship(Actor(x, y), tex: Int)`), `fun init(self)`, NO field defaults, every method implementing/replacing a trait or embedded-record method is `override fun`. Record embedding: `record World(Stage(camScale), ...)`.
|
|
16
|
+
- Grammar edits: `npx tree-sitter generate --disable-optimizations` in plum-tooling/tree-sitter-plum, then `npx tree-sitter test`. Never run `plum format` on real files.
|
|
17
|
+
- Std files `module std`, imported as std/game/<File>. camelCase. Floats compared `{a - b}.abs() < 1e-6`.
|
|
18
|
+
- Known compiler bug (R5): one local name bound to different record types in two match arms gives invalid wasm; use distinct names.
|
|
19
|
+
- Ownership defaults: ownership is on the type; plain params borrow (read-only); `own` param/`own self` takes the value; ordinary records unchanged. Error texts are exact: `"<name>" used after it was moved at <line>:<col>`, `cannot assign field "<f>" of borrowed "<name>"`, `cannot store borrowed "<name>"; pass it with own or move it`.
|
|
20
|
+
- Embedding an own record (or trait) makes the embedding record own (ruling from the review plan).
|
|
21
|
+
|
|
22
|
+
```mermaid
|
|
23
|
+
flowchart LR
|
|
24
|
+
MG[migrate phase A] --> UB[probe]
|
|
25
|
+
SY[syntax] --> CK1[checker moves] --> CK2[checker flow] --> LS[List own]
|
|
26
|
+
SY --> CG[in-place codegen] --> LS
|
|
27
|
+
LS --> SC[scene on own] --> EX[example + fixture] --> DO[docs]
|
|
28
|
+
MG --> SC
|
|
29
|
+
PI[Picture] --> SV[state/view game] --> SF[state/view fixture]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
File structure: each task's Owns. Spec coverage: What you will get (T3-T12), What will be built (T1-T13), How it fits together / How it gets built (diagrams), Decisions you can change (Global constraints), Risks (T2, T5, T6, T7), Out of scope (not built), How we will know it works (T4-T12), Rough time and cost (schedule).
|
|
33
|
+
|
|
34
|
+
## Task graph
|
|
35
|
+
```mermaid
|
|
36
|
+
flowchart LR
|
|
37
|
+
T1[migrate phase A]
|
|
38
|
+
T2[upper-bound probe]
|
|
39
|
+
T3[own syntax]
|
|
40
|
+
T4[checker moves]
|
|
41
|
+
T5[checker flow]
|
|
42
|
+
T6[in-place codegen]
|
|
43
|
+
T7[List own]
|
|
44
|
+
T8[scene on own]
|
|
45
|
+
T9[example + fixture]
|
|
46
|
+
T10[Picture]
|
|
47
|
+
T11[state/view game]
|
|
48
|
+
T12[state/view fixture]
|
|
49
|
+
T13[docs]
|
|
50
|
+
%% taskgraph
|
|
51
|
+
T1 --> T2
|
|
52
|
+
T3 --> T4
|
|
53
|
+
T4 --> T5
|
|
54
|
+
T3 --> T6
|
|
55
|
+
T5 --> T7
|
|
56
|
+
T6 --> T7
|
|
57
|
+
T1 --> T8
|
|
58
|
+
T7 --> T8
|
|
59
|
+
T8 --> T9
|
|
60
|
+
T10 --> T11
|
|
61
|
+
T11 --> T12
|
|
62
|
+
T9 --> T13
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Migration
|
|
66
|
+
|
|
67
|
+
### T1 migrate phase A to master syntax · Complexity 3 · Est. context 45k
|
|
68
|
+
**Owns:** plum-std/game/PureScene.plum, plum-std/game/SceneTree.plum, plum-std/game/SceneInput.plum, plum-std/game/Actions.plum, plum-examples/game_pure_scene.plum, plum-examples/struct_union_members.plum, plum-game/web-test/fixtures/stress_pure_scene.plum
|
|
69
|
+
**Components:** 2
|
|
70
|
+
**LOC owned:** 120
|
|
71
|
+
**Tests:** fast
|
|
72
|
+
**Model:** sonnet
|
|
73
|
+
**Consumes:** none
|
|
74
|
+
**Produces:** none
|
|
75
|
+
**Review Focus:** state: no behaviour or public API change; every existing test keeps its name and assertion.
|
|
76
|
+
**Test code:** existing tests only: PureScene 6, SceneTree 13, SceneInput 9, Actions 11, game_pure_scene 21, struct_union_members 5, all passing; `cargo test -p plum-game --test host_test pureScene` passes (2).
|
|
77
|
+
**Steps:** run each file, fix each error the master way: `override fun` on trait-implementing methods, constructors per header/`init`, replace removed field defaults with explicit args or `init`. Model after plum-std/game/Scene.plum on master. Report every language limitation hit.
|
|
78
|
+
|
|
79
|
+
### T2 upper-bound probe fixture · Complexity 2 · Est. context 30k
|
|
80
|
+
**Owns:** plum-game/web-test/fixtures/stress_pure_scene_inplace.plum
|
|
81
|
+
**Components:** 1
|
|
82
|
+
**LOC owned:** 150
|
|
83
|
+
**Tests:** slow
|
|
84
|
+
**Model:** sonnet
|
|
85
|
+
**Consumes:** none
|
|
86
|
+
**Produces:** none
|
|
87
|
+
**Review Focus:** numeric: same actor count, same draw output per frame as stress_pure_scene.plum.
|
|
88
|
+
**Test code:** `./plum-game/web-test/target/release/plum-web-test plum-game/web-test/fixtures/stress_pure_scene_inplace.plum --frames-wait 6 --json` prints a JSON line with `drawCalls` equal to stress_pure_scene.plum's.
|
|
89
|
+
**Steps:** copy stress_pure_scene.plum; make its per-frame update write actor fields in place (no clone, no spread, assign on the list element), keep everything else. Throwaway: unchecked, not a pattern for std. Do not run --headed benchmarks (coordinator does).
|
|
90
|
+
|
|
91
|
+
## Language
|
|
92
|
+
|
|
93
|
+
### T3 own syntax · Complexity 5 · Est. context 60k
|
|
94
|
+
**Owns:** plum-tooling/tree-sitter-plum/grammar.js, plum-tooling/tree-sitter-plum/src/parser.c, plum-tooling/tree-sitter-plum/src/grammar.json, plum-tooling/tree-sitter-plum/src/node-types.json, plum-tooling/tree-sitter-plum/test/corpus/own.txt, plum-tooling/tree-sitter-plum/queries/plum/highlights.scm, plum-tooling/tree-sitter-plum/queries/plum/format.scm, plum-core/src/ast.rs, plum-core/src/parser.rs, plum-core/tests/own_parse_test.rs
|
|
95
|
+
**Components:** 2
|
|
96
|
+
**LOC owned:** 200
|
|
97
|
+
**Tests:** fast
|
|
98
|
+
**Model:** sonnet
|
|
99
|
+
**Consumes:** none
|
|
100
|
+
**Produces:** ast::Enum.isOwn: bool · ast::TraitDecl.isOwn: bool · ast::Param.isOwn: bool · ast::Fn.ownSelf: bool
|
|
101
|
+
**Review Focus:** encoding: `own` stays a valid identifier prefix (`owner := 1`, `fun ownBy()` parse unchanged); format round-trips `own record`.
|
|
102
|
+
**Test code:** own_parse_test.rs: `own_record_flag` (`own record Sprite(x: Float)` → isOwn true; `record P(x: Float)` → false); `own_trait_flag`; `own_self_flag` (`fun act(own self, dt: Float) -> Actor` → ownSelf true, params[0] dt); `own_param_flag` (`fun put(own v: Sprite)` → params[0].isOwn); `owner_identifier_still_parses`. corpus own.txt with the same four forms. Every existing parser/formatter/corpus test passes.
|
|
103
|
+
**Steps:** grammar optional `own` before record/trait and in params (`own self`, `own name: T`); regen with --disable-optimizations; parser sets the flags; every other AST constructor gets false; highlights `own` as keyword.
|
|
104
|
+
|
|
105
|
+
### T4 ownership checker: bindings and moves · Complexity 6 · Est. context 65k
|
|
106
|
+
**Owns:** plum-checker/src/ownership.rs, plum-checker/src/lib.rs, plum-checker/tests/ownership_moves_test.rs
|
|
107
|
+
**Components:** 1
|
|
108
|
+
**LOC owned:** 260
|
|
109
|
+
**Tests:** fast
|
|
110
|
+
**Model:** sonnet
|
|
111
|
+
**Consumes:** ast::Enum.isOwn: bool · ast::Param.isOwn: bool · ast::Fn.ownSelf: bool
|
|
112
|
+
**Produces:** ownership::checkOwnership(src: &ast::Source) -> Vec<CheckError>
|
|
113
|
+
**Review Focus:** state: passing an own binding to an `own` param, returning it, storing it in a field/list, or using it as a spread source moves it; reading a field or passing to a plain param borrows. deep: method calls with `own self` move the receiver.
|
|
114
|
+
**Test code:** ownership_moves_test.rs, each a source run through checkSource, exact messages: `use_after_move_to_own_param`, `use_after_spread_move` (`t := S(..s, x: 1.0)` then `s.x`), `use_after_own_self_call`, `reassign_revives` (`s = s.step()` then `s.x` ok), `borrow_param_field_write_rejected` (`cannot assign field "x" of borrowed "s"`), `own_param_field_write_allowed`, `store_borrow_rejected` (plain param pushed into a List → `cannot store borrowed "s"; pass it with own or move it`), `ordinary_record_unaffected` (aliasing a plain record still compiles), `message_has_move_position` (`at 3:12`).
|
|
115
|
+
**Steps:** new pass called from checkSource after lowering; walk fn bodies straight-line only (branches/loops/closures: treat conservatively as "moved if moved anywhere", refined in T5).
|
|
116
|
+
|
|
117
|
+
### T5 ownership checker: branches, loops, closures, fields · Complexity 6 · Est. context 60k
|
|
118
|
+
**Owns:** plum-checker/src/ownership.rs, plum-checker/tests/ownership_flow_test.rs
|
|
119
|
+
**Components:** 1
|
|
120
|
+
**LOC owned:** 220
|
|
121
|
+
**Tests:** fast
|
|
122
|
+
**Model:** sonnet
|
|
123
|
+
**Consumes:** ownership::checkOwnership(src: &ast::Source) -> Vec<CheckError>
|
|
124
|
+
**Produces:** none
|
|
125
|
+
**Review Focus:** state: joins (if/match arms, early return), loop bodies, closures, embedded own records. R5: no new invalid wasm in match arms.
|
|
126
|
+
**Test code:** ownership_flow_test.rs: `moved_in_one_branch_used_after_join` (error), `moved_in_both_branches_then_reassigned_ok`, `early_return_move_ok` (moved only on a returning path, used after), `loop_move_without_reassign` (error `"s" used after it was moved` pointing in-loop), `loop_move_with_reassign_ok` (`s = s.step()` each iteration), `closure_capture_borrows_only` (closure moving own capture → error; reading ok), `swap_field_form_ok` (`w.stage = w.stage.frame(dt)` where w is an ordinary record holding own Stage), `field_move_without_swap_rejected`, `embedding_own_makes_host_own` (`record World(Stage, k: Int)` with own Stage: aliasing World → move error), `match_arm_rename_still_valid_wasm` (compile + run), `list_copy_of_own_rejected` (R2: a std method that returns a T read from a List[T] without moving, e.g. `xs.at(0)` stored into another list, errors naming the method/site).
|
|
127
|
+
|
|
128
|
+
### T6 in-place spread codegen · Complexity 5 · Est. context 70k
|
|
129
|
+
**Owns:** plum-wasm-codegen/src/lib.rs, plum-wasm-codegen/tests/own_inplace_e2e_test.rs
|
|
130
|
+
**Components:** 1
|
|
131
|
+
**LOC owned:** 140
|
|
132
|
+
**Tests:** fast
|
|
133
|
+
**Model:** sonnet
|
|
134
|
+
**Consumes:** ast::Enum.isOwn: bool
|
|
135
|
+
**Produces:** none
|
|
136
|
+
**Review Focus:** state: in-place only when the spread source is a local/param of the SAME own record (or own-trait record) type as the construction; anything else copies. R3: trait field writes via `__set_` on owned values compile.
|
|
137
|
+
**Test code:** own_inplace_e2e_test.rs (run wasm): `own_spread_reuses_object` (a marker field written on own `s` before `t := S(..s, x: 1.0)` is visible on `t`, and no allocation: same object), `plain_spread_still_copies` (ordinary record: source unchanged after spread), `own_spread_sets_fields` (values correct), `own_trait_record_spread_in_place`, `own_self_field_write` (`fun bump(own self) -> S = { self.x = self.x + 1.0; self }`).
|
|
138
|
+
**Steps:** in `compileValueConstruction`/`compileVariantConstruction` (call.spread at ~7442/8012) branch on own source: evaluate source, `struct.set` each named field, leave source as result. No checker dependency in tests (sources are valid by construction).
|
|
139
|
+
|
|
140
|
+
### T7 List for owned elements · Complexity 4 · Est. context 45k
|
|
141
|
+
**Owns:** plum-std/List.plum, plum-examples/own_list.plum
|
|
142
|
+
**Components:** 1
|
|
143
|
+
**LOC owned:** 130
|
|
144
|
+
**Tests:** fast
|
|
145
|
+
**Model:** sonnet
|
|
146
|
+
**Consumes:** ownership::checkOwnership(src: &ast::Source) -> Vec<CheckError>
|
|
147
|
+
**Produces:** List.mapInPlace(own self, f: fn(T) -> T) -> List[T] · List.take(self, i: Int) -> T · List.put(self, i: Int, own v: T)
|
|
148
|
+
**Review Focus:** state R2: no List method duplicates an own element when instantiated with an own T; `List.copy`-style methods error at the instantiation naming the method.
|
|
149
|
+
**Test code:** own_list.plum tests: `map_in_place_updates_all` (3 own sprites, x+1 each), `take_then_put_round_trip`, `at_borrows` (reading `xs.at(0).x` twice ok), `put_after_take_keeps_length`; sweep List.plum and report each method that duplicates T (the T5 test `list_copy_of_own_rejected` covers the error). e.g. `[S(x: 1.0)].mapInPlace(|s| S(..s, x: s.x + 1.0)).at(0).x -> 2.0`.
|
|
150
|
+
|
|
151
|
+
## Scene on own
|
|
152
|
+
|
|
153
|
+
### T8 scene modules on own · Complexity 5 · Est. context 60k
|
|
154
|
+
**Owns:** plum-std/game/PureScene.plum, plum-std/game/SceneTree.plum, plum-std/game/SceneInput.plum, plum-std/game/Actions.plum
|
|
155
|
+
**Components:** 3
|
|
156
|
+
**LOC owned:** 200
|
|
157
|
+
**Tests:** fast
|
|
158
|
+
**Model:** sonnet
|
|
159
|
+
**Consumes:** List.mapInPlace(own self, f: fn(T) -> T) -> List[T] · List.take(self, i: Int) -> T · List.put(self, i: Int, own v: T)
|
|
160
|
+
**Produces:** none
|
|
161
|
+
**Review Focus:** state: `clone()` removed; Actor is `own trait`, leaves `own record`; update/act/modify take `own`. Same test names and expected values as before.
|
|
162
|
+
**Test code:** all existing tests in the four files pass unchanged in name and asserted values (39 total).
|
|
163
|
+
|
|
164
|
+
### T9 example and fixture on own · Complexity 3 · Est. context 45k
|
|
165
|
+
**Owns:** plum-examples/game_pure_scene.plum, plum-game/web-test/fixtures/stress_pure_scene.plum
|
|
166
|
+
**Components:** 2
|
|
167
|
+
**LOC owned:** 100
|
|
168
|
+
**Tests:** slow
|
|
169
|
+
**Model:** sonnet
|
|
170
|
+
**Consumes:** none
|
|
171
|
+
**Produces:** none
|
|
172
|
+
**Review Focus:** state: game_pure_scene 21 tests unchanged; host parity and browser check still pass.
|
|
173
|
+
**Test code:** `plum test plum-examples/game_pure_scene.plum` 21/0; `cargo test -p plum-game --test host_test pureScene`; `cargo test --manifest-path plum-game/web-test/Cargo.toml --test web_check pure_scene_renders_and_animates`.
|
|
174
|
+
|
|
175
|
+
## State/view
|
|
176
|
+
|
|
177
|
+
### T10 std/game/Picture · Complexity 4 · Est. context 40k
|
|
178
|
+
**Owns:** plum-std/game/Picture.plum
|
|
179
|
+
**Components:** 1
|
|
180
|
+
**LOC owned:** 180
|
|
181
|
+
**Tests:** fast
|
|
182
|
+
**Model:** sonnet
|
|
183
|
+
**Consumes:** none
|
|
184
|
+
**Produces:** Picture.draw(node: SceneNode) · Picture.bounds(node: SceneNode) -> Rect · Picture.hitPath(node: SceneNode, x: Float, y: Float) -> List[Int]
|
|
185
|
+
e.g. `Picture.hitPath(Group([Translate(10.0, 0.0)], [RectShape(0.0, 0.0, 10.0, 10.0, c)]), 15.0, 5.0) -> [0]`
|
|
186
|
+
e.g. `Picture.hitPath(Group([], [RectShape(0.0, 0.0, 10.0, 10.0, c)]), 50.0, 50.0) -> []`
|
|
187
|
+
**Review Focus:** numeric: transforms compose in tree order; Filter alpha multiplies; renderTarget restored after Filter.
|
|
188
|
+
**Test code:** types `enum Transform = Translate | Rotate | Scale` (structs), `enum Shape = RectShape | CircleShape | LineShape` (structs), records Sprite, Text, `Group(transform: List[Transform], children: List[SceneNode])`, `Filter(effect: Effect, child: SceneNode)`, `enum Effect = Tint | Alpha | Blend`, `enum SceneNode = Shape | Sprite | Text | Group | Filter`. Tests: `bounds_translate_scale` (10x10 rect under Translate(5,5), Scale(2,2) → Rect(5,5,20,20)), `bounds_rotate_quarter`, `hit_path_nested` (→ [1]), `hit_miss_empty`.
|
|
189
|
+
|
|
190
|
+
### T11 state/view Orb Run · Complexity 5 · Est. context 55k
|
|
191
|
+
**Owns:** plum-examples/game_state_view.plum
|
|
192
|
+
**Components:** 2
|
|
193
|
+
**LOC owned:** 260
|
|
194
|
+
**Tests:** fast
|
|
195
|
+
**Model:** sonnet
|
|
196
|
+
**Consumes:** Picture.draw(node: SceneNode) · Picture.hitPath(node: SceneNode, x: Float, y: Float) -> List[Int]
|
|
197
|
+
**Produces:** none
|
|
198
|
+
**Review Focus:** state: same gameplay as game_pure_scene (ship, orbs, mines, score, restart); state records keyed by id; `update(state, msg) -> State` pure; `view(state) -> SceneNode` pure.
|
|
199
|
+
**Test code:** mirror game_pure_scene's gameplay tests by name where they apply (`ship_moves_on_input`, `orb_pickup_scores`, `mine_hit_ends_game`, `restart_resets`, ...) plus `view_is_pure` (same state → equal node counts) and `view_node_count` (N orbs → N+K nodes).
|
|
200
|
+
|
|
201
|
+
### T12 state/view fixture and web check · Complexity 3 · Est. context 35k
|
|
202
|
+
**Owns:** plum-game/web-test/fixtures/stress_state_view.plum, plum-game/web-test/tests/web_check.rs
|
|
203
|
+
**Components:** 2
|
|
204
|
+
**LOC owned:** 140
|
|
205
|
+
**Tests:** slow
|
|
206
|
+
**Model:** sonnet
|
|
207
|
+
**Consumes:** none
|
|
208
|
+
**Produces:** none
|
|
209
|
+
**Review Focus:** numeric: same actor count (about 5,750) and visual density as stress_pure_scene.
|
|
210
|
+
**Test code:** web_check.rs `state_view_renders_and_animates` (mirrors pure_scene_renders_and_animates) passes in Chromium.
|
|
211
|
+
|
|
212
|
+
## Docs
|
|
213
|
+
|
|
214
|
+
### T13 docs · Complexity 2 · Est. context 30k
|
|
215
|
+
**Owns:** README.md
|
|
216
|
+
**Components:** 1
|
|
217
|
+
**LOC owned:** 60
|
|
218
|
+
**Tests:** fast
|
|
219
|
+
**Kind:** mechanical
|
|
220
|
+
**Model:** haiku
|
|
221
|
+
**Consumes:** none
|
|
222
|
+
**Produces:** none
|
|
223
|
+
**Review Focus:** encoding: examples in README compile as written.
|
|
224
|
+
**Test code:** README "Own records" subsection: `own record`, `own self`, the three error messages verbatim from Global constraints, the swap form, one in-place spread example; link to game_state_view.plum.
|
|
225
|
+
|
|
226
|
+
## Review Focus
|
|
227
|
+
- compiler (library/parser): deep, state, encoding. Risks: use-after-move through joins/loops/closures; own spread on a different type must copy; `own` keyword vs identifiers.
|
|
228
|
+
- std/game + examples: state, numeric. Risks: behaviour drift from phase A; Picture matrix order.
|
|
229
|
+
**Probe entry:** run=./target/debug/plum test
|
|
230
|
+
R4 gate: coordinator benchmarks T2; if it misses the bar, stop and report.
|