On this page
- 1. Which contract is my model using?
- 2. Lifecycle differences at a glance
- 3. Shared neighborhood changes are not a lifecycle switch
- 4. Scheduling and population caveats
- 5. Older entry points and safer choices
- 6. Project, Reset, and saved settings
- 7. A controlled migration, not a flag-only edit
- 8. Evidence and limits
Compatibility and known limitations
Manual contents | Lifecycle | Troubleshooting | Alphabetical API index
Use this appendix when reading an older model, deciding whether an old helper is suitable, or planning a deliberately selected lifecycle migration. It consolidates compatibility advice; the linked references retain the exact signatures, examples, and detailed contracts. No runtime behavior is changed by this appendix.
1. Which contract is my model using?
Only the exact top-level Project setting lifecycle: "aligned" selects the
aligned patch/agent lifecycle. Omission, "legacy", and any other value retain
legacy behavior. Project format version is a separate setting; increasing it
does not opt into aligned lifecycle.
The tutorials and practical chapters teach aligned models. Existing templates, unconverted apps, saved models, and external clients must not be assumed aligned. Do not copy constructor-driven initialization from an older example into a new aligned model, or add the flag to an old model without reviewing its hooks. Changing lifecycle requires reinitializing the application, not changing a live runtime field or pressing ordinary Reset.
The flag changes initialization, reset ownership, birth setup, and built-in updater evaluation. It does not redesign visit modes, movement, spatial indexing, or step/shift boundaries, and does not change SD initialization. Some shared API repairs apply in both modes; see shared neighborhood changes.
For the full contract, read aligned lifecycle. The earlier lifecycle sections explicitly describe legacy behavior.
2. Lifecycle differences at a glance
These are base/runtime contracts. A model's own overrides can add calls or change behavior; the flag does not remove those calls for you.
| Surface | Legacy path | Aligned path / migration rule |
|---|---|---|
| Constructor | Older templates call init from subclass constructors, sometimes before defaults exist | Allocate stable fields/Stores only; do not call init/setup |
| Main init | Runs during runtime initialization and again from base world reset | Runs once after cell context/parameters, before populations and agent configuration; preserve super.init |
| Patch init | Base constructor does not invoke it; template/model code arranges it | Runtime applies initial colors before calling each patch's polymorphic init once |
| First setup | Patch setup is not automatically coordinated; initial turtle setup can repeat; Main setup is not automatically coordinated | After configured populations exist: patch setup, turtle setup, Main setup, once each |
| Main reset | Base reset orchestrates the world and calls Main init/setup | Main reset is local; resetWorld orchestrates the world and coordinated setup |
| Turtle local reset | Restores its reset-list Stores, then calls setup | Restores its reset-list Stores without setup |
| World Reset identities | Main and patches survive; turtles are recreated | Same identity policy; rebuild references to replacement turtles in setup |
| Base patchStep | Empty | Evaluates assigned colorUpdater |
| Base turtleStep | Schedules trajectory movement | Movement first, then assigned theta/color/size/shape updaters |
| Birth helpers | Do not assume the aligned init/customize/deferred-setup sequence | Constructor, inherited/default built-ins, init, customization; setup after batch insertion/indexing |
Preserve base step calls and remove duplicate manual built-in updater evaluation when migrating. Custom Store updaters still need explicit evaluation. Do not reorder movement and heading updates accidentally.
Calling Main reset locally is not a safe substitute for a world reset in aligned mode: it clears local queues/time but does not recreate populations, restore patches, or halt the whole world. Let browser/dispatcher Reset orchestrate it, or deliberately use resetWorld. See reset ownership.
What aligned mode does not infer
- Synchronization commits a custom Store; it does not define its reset policy. Main and Patch overrides do not automatically restore every inherited Store.
- Store values are not cloned. Use a reset factory for fresh mutable values.
- Setup repeats after world reset. Register stable listeners once in init or construction, not on every setup.
- Recreated turtles can leave registrations/resources on surviving owners. Models must define disposal; opt-in is not automatic leak repair.
- A configured agent population of size zero still requires agent initialization. Main init cannot assume that population already exists.
See state ownership and Store contracts.
External clients
Clients opting in through initCells messages must also supply
awaitAgents: true when agents will follow. For aligned agent worlds, the
first cellinit is a request handshake, not final display data. After setup,
agentinit supplies agents and worldinit supplies final patch/display state;
do not request the agents again on worldinit. Patch-only initialization uses
cellinit after setup. Legacy message ordering is unchanged.
Use the browser/runtime protocol; do not implement a reset-before-first-run workaround.
3. Shared neighborhood changes are not a lifecycle switch
The repaired query contracts apply in both aligned and legacy modes. Removing the aligned flag does not restore older duplicate, wrapping, or validation behavior.
| Current contract | Consequence for older model rules |
|---|---|
| Wrapped grid queries use modulo normalization and unique patch results | Large/tiny-world queries no longer count repeated images as separate neighbors |
| Visitors call once per distinct patch, matching collection forms | Do not expect duplicate callback effects at seams |
| block includes the center; ring/blockX exclude it | Choose explicitly whether the focal patch belongs in a calculation |
| Grid distance is a nonnegative safe integer | Fractional, negative, nonfinite, and other invalid distances throw RangeError; decide the model's rounding policy before calling |
| ring(0)/blockX(0) are empty; block(0) is the focal patch | A zero-radius input has defined behavior, not an implicit first ring |
| nhood4 respects boundaries and requires square topology | Use topology-neutral neighbors for hex; do not assume four hex side neighbors |
| Local turtle queries deduplicate, hide newborn/dying entries, and exclude the caller | Count results agree with the corresponding unique-other-turtle query |
| Radius/annulus queries use Euclidean distance with shortest wrapped displacement | Do not substitute square/hex graph rings for physical-radius selection |
| Object-form APIRoot.distance reads both axes | Results are raw coordinate distances, not automatically wrapped or hex-scaled |
These are intentional shared repairs, not proof that an entire legacy app has been migrated. See query contracts and neighborhood stabilization evidence. In particular, choose a scientific meaning for a fractional grid-distance input rather than silently truncating it to make an exception disappear.
4. Scheduling and population caveats
These limitations remain relevant in aligned mode as well.
| Caveat | Recommended practice |
|---|---|
| C/A and A/C currently omit mapped patch/turtle shift hooks, although Store commitment still occurs | Do not depend on those hooks in slash modes; test a design with another visit mode or put proposal work in the appropriate step rule |
| doTurtleShift is not consulted by current scheduler maps | Do not use it as an on/off switch for turtle hooks |
| Persistent and transient patch passes can both visit a patch; visitAll can make multiple passes scan everything | Choose one activation policy for a simple model; do not assume visitAll means exactly once per tick |
| Intermediate slash-mode shifts can insert births and remove deaths | Test same-tick opportunities explicitly; C/A is not equivalent to C_A |
| A dying turtle remains in the population until shift but disappears from local queries earlier | Return after die when the current rule should stop; distinguish population size from query visibility |
| step and shift are internal phases, not browser Step | Use browser controls; update is the normal post-commit reporting hook |
| Main statusToJson consumes output/reporting queues | Do not call it as a harmless diagnostic snapshot |
See shift-hook limitations, activation controls, and birth/death timing.
5. Older entry points and safer choices
This is a triage table, not a promise that every unlisted helper is certified. Some names are merely internal; others are defective or have surprising contracts. Consult the reference before replacing one: a similar name does not imply equivalent semantics.
| Entry point / issue | Prefer / consult |
|---|---|
| Main base initAgents has an effective legacy overload, not a usable empty hook | Override it and call initializeTurtles with your Turtle class; do not call super.initAgents as a no-op. Population initialization |
| Main dt numeric field shadows the prototype dt method | Read this.dt, do not call it. Time/control |
| Main nlDiffuse has unresolved boundary/loop limitations | Use the explicit synchronous Heat model; preserve its conservation and boundary checks |
| askTurtles/filterBreed depend on legacy breed/filter machinery | Explicitly filter an array of turtles with instanceof and iterate it. Collection scans |
| Turtle forward returns a heading; fd is defective; initDynamics uses obsolete Store-style assumptions | Use pending movement and explicit built-in/custom initialization, not these as movement or cloning APIs. Movement reference |
| nextX_val/nextY_val and XVal/YVal listeners refer to absent Stores | Use joint velocity operations and XVel/YVel interfaces. State, notifications |
| Turtle link lacks the coordinates required by Main poke | Leave insertion/index changes to runtime-managed births and movement. Index operations |
| patchAtAngle contains boundary/geometry defects | Use checked patch_ahead or explicit patch queries with a known geometry policy. Sensing |
| Trails, sniffer, and sniffCone have sampled-path and swallowed-error limitations | Use explicit checked queries/rules; these helpers are not general collision or physical sensing APIs. Legacy sampling |
| Coords.equals calls numeric internals as functions; hexify mutates and is not idempotent | Compare public numeric coordinates explicitly; distinguish raw addresses from physical coordinates. Coordinate contracts |
| NLRoot.filter matrix overload passes columns, not patches; class/iterator dispatch has caveats | Use patches.toList().filter(predicate) or an explicit array conversion. Filter overloads |
| max_one_of_value spreads tuple elements, unlike min_one_of_value | Use an explicit scalar aggregation with a chosen empty policy. Extrema |
| Random interval truthy-defaults mishandle zero bounds; random and irandom have separate generators | For ordered real bounds use low + (high - low) * NLRoot.random(); do not assume Reset rewinds a common seed. Random helpers |
| normalize does not validate finite input; negative Infinity can loop forever | Validate finiteness before calling; do not use nonfinite headings. Geometry |
| APIRoot.shuffle mutates and returns undefined; NLRoot.shuffle returns the mutated array | Choose the declaring class deliberately. Sampling |
| AgentSet constructor is not an iterable initializer; toString returns an array; sorting policies differ | Use supplied query results, explicit iteration, and the documented collection shape. Agent collections |
Immediate built-in assignments replace reset baselines; current setters retain them; next setters propose for later commitment. Do not repair a legacy helper by manually shifting private Stores. See read/set/propose.
6. Project, Reset, and saved settings
| Surprise | Deliberate policy |
|---|---|
| Numeric default speed 0 (and size 0) falls back to 1 | Apply explicit public state in setup/step for a stoppable model; use visibility controls rather than zero size |
| A speed/theta input binding updates existing turtles, but Reset can restore captured starts | If today's input must govern Reset, read it explicitly in setup and in the intended live rule |
| Changing an input used for initial population count does not resize on ordinary Reset | Reinitialize with current inputs, or design births/deaths; do not mistake a count binding for a live resize command |
| Nonempty explicit turtle starts override count; IDs are not durable identities | Verify starts and population, and rebuild references after world reset |
| dt advances simulation time, not automatic trajectory distance scaling | Scale the model rule explicitly when that is the intended unit policy |
| Agent boundary and cell boundary are independent | Test motion and sensing at edges separately |
| Saved/shared presets may override Project starts or inputs | Inspect the loaded preset before diagnosing a failed code change |
| hardReset or a model-specific Reset override changes ordinary Reset behavior | Check the actual Project and override; ordinary reset advice is not a promise about custom run controls |
Format version 0.2 is used by the guide; a newer value can produce a compatibility warning. Save/relaunch for code changes. Ordinary Reset is not recompilation, full configuration reload, or random-stream replay. Use the Project reference, especially its live change / Reset / relaunch table.
7. A controlled migration, not a flag-only edit
App migrations remain deferred unless explicitly selected. For current app/copy status and the detailed process, use the migration record and freshening checklist. Canonical apps and embedded Studio copies can differ; compare each with its own previous behavior.
For a selected model:
- Capture startup state, a small controlled trajectory, outputs, input effects, births/deaths, and repeated reset. Record existing failures separately.
- Move work to constructor/init/local reset/setup according to ownership. Preserve scientific rules, draw order, visit mode, and phase boundaries.
- Keep stable registrations on surviving owners; dispose turtle-owned resources on replacement/death. Review early overridden setters before moving fields.
- Opt in only after removing manual lifecycle orchestration and duplicate built-in updater calls. Review initial and birth customization callbacks.
- Compare time zero, steps, zero-agent worlds, parameter changes, two resets, and initial rendering. Distinguish intended reset repairs from trajectory parity; do not silently accept new behavior.
8. Evidence and limits
The existing focused suites provide executable evidence:
- Guide examples: actual Markdown examples, state/phase contracts, selected legacy defects, declaration inventories, alphabetical lookup, and relative file/heading links including this appendix.
- Aligned lifecycle: hook order, stable ownership, births, factories/errors, reset, and browser protocol.
- Legacy compatibility: characterization of retained legacy execution and helper behavior.
- Neighborhoods: shared query repairs, boundaries, uniqueness, radii, and selected affected callers in both copies.
- Utility edge cases: sampling validation and string-tokenization contracts.
Run these from the repository root:
node --test testing/nlapi-guide.test.mjs testing/nlapi-aligned-lifecycle.test.mjs testing/nlapi-compatibility.test.mjs testing/nlapi-neighborhoods.test.mjs testing/nlroot-utilities.test.mjs
These headless checks do not certify every saved/external model, statistical distribution, host iterator API, SD/Stock/collision branch, or browser layout/launch flow. A characterized defect is not a supported modeling feature, and documenting it is not repairing it. Use a small, measurable model check before relying on a behavior outside the verified guide path.