|
7 | 7 |
|
8 | 8 | [NOTE] |
9 | 9 | ==== |
10 | | -**Status: design-translated, toolchain run pending.** The translation is |
11 | | -written to be compilable against AffineScript v0.1.0 (Int arithmetic, no |
12 | | -external module imports), following the same Float→Int convention |
13 | | -xref:idaptik-hitbox.adoc[idaptik-hitbox] established. A Reproducibility |
14 | | -block matching hitbox's will be filled in by the next translator who runs |
15 | | -this through `affinescript-host-shim.sh`. |
| 10 | +**Status: compiled. 952-byte WASM MVP module produced 2026-05-02 via the |
| 11 | +containerized v0.1.0 toolchain.** Two v0.1.0 expedients applied: Float → |
| 12 | +Int with documented scaling (per xref:idaptik-hitbox.adoc[idaptik-hitbox]); |
| 13 | +and `mut Player` with field assignment substituted for an immutable |
| 14 | +*returning-new* style, because the `mut`-borrow-with-struct-field-assignment |
| 15 | +idiom is not yet verified in the v0.1.0 surface (`examples/ownership.affine` |
| 16 | +shows `mut` as a parameter mode but no in-body assignment example). |
16 | 17 |
|
17 | 18 | Picked because it stress-tests the migration playbook's central decision — |
18 | 19 | *`mut` vs `State` effect* — on a record with three orthogonal aspects |
@@ -247,6 +248,41 @@ of a public field** — the source-comment header should declare the |
247 | 248 | scaling factors. When AffineScript gains Float-arithmetic operators, the |
248 | 249 | file can be reverted to floats with a one-pass rewrite. |
249 | 250 |
|
| 251 | +=== 6. `mut Player` design intent → immutable returning-new (v0.1.0 expedient) |
| 252 | + |
| 253 | +The design above shows `take_damage(p: mut Player, ...)` and `update(p: mut |
| 254 | +Player, dt_ms: Int) -> Velocity`. The shipped `PlayerHP.affine` does |
| 255 | +**not** use that signature. It uses an immutable returning-new style: |
| 256 | + |
| 257 | +[source,affinescript] |
| 258 | +---- |
| 259 | +fn take_damage(p: Player, amount: Int, source: Pos, player: Pos) -> Player |
| 260 | +fn update(p: Player, dt_ms: Int) -> UpdateResult // { player, velocity } |
| 261 | +---- |
| 262 | + |
| 263 | +Why: `mut` is a real parameter mode in v0.1.0 |
| 264 | +(`examples/ownership.affine` shows `fn takes_mut(x: mut Bool) -> () = ();`) |
| 265 | +but the in-body idiom for *assigning to a struct field through a `mut` |
| 266 | +borrow* — the natural way to write `p.hp.current = ...` in the lesson's |
| 267 | +design — is not demonstrated by any v0.1.0 example I could find, and |
| 268 | +attempting it without verification risked the kind of speculative-syntax |
| 269 | +bug a lesson should not propagate. |
| 270 | + |
| 271 | +The immutable returning-new form preserves the *semantics* of the |
| 272 | +re-decomposition (every mutator decision is still made; aspects are |
| 273 | +still fissioned; the Player record is still composed) at the cost of |
| 274 | +each mutator allocating a fresh struct. This is acceptable for a small |
| 275 | +record and a 60fps game tick; it is not the long-term design. |
| 276 | + |
| 277 | +**This is the same shape of expedient as Float → Int**: a v0.1.0 |
| 278 | +compiler limitation forces a syntactic compromise that does not change |
| 279 | +the design. Document it at the file header; rewrite when the upstream |
| 280 | +gap closes. |
| 281 | + |
| 282 | +The `mut` vs `State` *decision* the lesson is about — local mutation in |
| 283 | +one tick under one owner — is unaffected. It just gets expressed |
| 284 | +immutably for now. |
| 285 | + |
250 | 286 | == Playbook verdict |
251 | 287 |
|
252 | 288 | === Held up cleanly |
@@ -303,25 +339,26 @@ When translating a `.res` file with **multi-field mutable state**: |
303 | 339 |
|
304 | 340 | == Reproducibility |
305 | 341 |
|
306 | | -[NOTE] |
307 | | -==== |
308 | | -This lesson is design-translated; the toolchain run is pending. The |
309 | | -steps below match xref:idaptik-hitbox.adoc#reproducibility[the hitbox |
310 | | -lesson's Reproducibility section] and should produce the same shape of |
311 | | -output. |
312 | | -==== |
| 342 | +The translation, the typecheck, the compile, and the WASM emission can |
| 343 | +be reproduced from the IDApTIK repository at HEAD as of 2026-05-02: |
313 | 344 |
|
314 | 345 | [source,bash] |
315 | 346 | ---- |
316 | | -$ just affinescript-stage |
317 | 347 | $ ./scripts/affinescript-host-shim.sh check src/app/combat/PlayerHP.affine |
318 | | -# Expected: "Type checking passed" |
| 348 | +Type checking passed |
| 349 | +
|
319 | 350 | $ ./scripts/affinescript-host-shim.sh compile src/app/combat/PlayerHP.affine -o src/app/combat/PlayerHP.wasm |
320 | | -# Expected: "Compiled src/app/combat/PlayerHP.affine -> src/app/combat/PlayerHP.wasm (WASM)" |
| 351 | +Compiled src/app/combat/PlayerHP.affine -> src/app/combat/PlayerHP.wasm (WASM) |
| 352 | +
|
| 353 | +$ ls -la src/app/combat/PlayerHP.wasm |
| 354 | +-rw-r--r-- 1 hyperpolymath hyperpolymath 952 May 2 08:12 src/app/combat/PlayerHP.wasm |
| 355 | +
|
| 356 | +$ file src/app/combat/PlayerHP.wasm |
| 357 | +src/app/combat/PlayerHP.wasm: WebAssembly (wasm) binary module version 0x1 (MVP) |
321 | 358 | ---- |
322 | 359 |
|
323 | | -When the toolchain run completes, replace this NOTE with the actual |
324 | | -output (matching hitbox's format) and link the resulting `.wasm` size in |
325 | | -the file header. If the run fails on a v0.1.0 limitation not anticipated |
326 | | -here, **edit the lesson** — that is exactly the case study a future |
327 | | -translator most needs. |
| 360 | +The IDApTIK side of this lesson lives at `idaptik/src/app/combat/PlayerHP.affine` |
| 361 | +(translation, 144 lines) and `idaptik/src/app/combat/PlayerHP.res` (original, |
| 362 | +88 lines). The compiled `.wasm` is 952 bytes — roughly 2.5× hitbox's 384 |
| 363 | +bytes, accounting for the larger function surface (`make`, `is_invincible`, |
| 364 | +`is_alive`, `take_damage`, `update`, plus the four tuning fns and `max`). |
0 commit comments