Record model guidance for Jerboa scripts

ober

795d047caef9659f398dacf08f182b7069b5db44

diff --git a/docs/model-coding-guidance.md b/docs/model-coding-guidance.md
new file mode 100644
index 0000000..1d9667f
--- /dev/null
+++ b/docs/model-coding-guidance.md
@@ -0,0 +1,47 @@
+# Model Coding Guidance for Jerboa Scripts
+
+This document records implementation facts that coding agents should treat as
+hard constraints when writing standalone Jerboa scripts, especially verifier-led
+benchmark tasks.
+
+## Atoms
+
+Use the real prelude atom API:
+
+- Create state with `(atom initial)`.
+- Read state with `(atom-deref a)` or `(deref a)`.
+- Replace state with `(atom-reset! a value)` or `(reset! a value)`.
+- Update state with `atom-swap!`, `atom-update!`, or `swap!`.
+
+Do not invent names such as `atom-set!` or `atom-val`.
+
+## Environment Variables
+
+Use `(getenv "NAME")` for environment variables. It returns a string or `#f`.
+Do not use guessed names such as `get-environment-variable`.
+
+## Vector Grids
+
+For mutable rectangular grids, prefer a vector of row vectors:
+
+```scheme
+(def (make-grid height width fill)
+  (let ((rows (make-vector height)))
+    (do ((r 0 (+ r 1)))
+        ((= r height) rows)
+      (vector-set! rows r (make-vector width fill)))))
+```
+
+Each row slot must remain a row vector. Do not store the whole grid into one
+row slot during compaction or line clearing. Cell access should return the cell
+value, not a row or grid vector.
+
+## Indexed Constants
+
+Keep ids and vector indexes aligned. If `0` means empty and active ids are
+stored as `1..N`, then a vector of `N` shapes is indexed with `(- id 1)`.
+Apply the same conversion at every access site, including collision, locking,
+and drawing helpers.
+
+If quoted RGB tuples are lists, read components with `list-ref`. If component
+access uses `vector-ref`, define the RGB rows as vectors.