@neocompose/cli 0.87.2 → 0.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +14 -0
  3. package/dist/neo-runtime.mjs +11153 -3060
  4. package/dist/test-worker.mjs +15214 -9707
  5. package/package.json +1 -1
  6. package/skills/neocompose-cli/SKILL.md +95 -279
  7. package/skills/neocompose-cli/references/cli-development.md +101 -117
  8. package/skills/neocompose-cli/references/declarations/annotations-and-storage.md +112 -0
  9. package/skills/neocompose-cli/references/declarations/constructors.md +164 -0
  10. package/skills/neocompose-cli/references/declarations/declarations.md +103 -0
  11. package/skills/neocompose-cli/references/declarations/extensions.md +46 -0
  12. package/skills/neocompose-cli/references/declarations/initializers.md +100 -0
  13. package/skills/neocompose-cli/references/declarations/member-kinds.md +107 -0
  14. package/skills/neocompose-cli/references/declarations/overloads.md +50 -0
  15. package/skills/neocompose-cli/references/dialogues.md +145 -0
  16. package/skills/neocompose-cli/references/neoscript/collections.md +138 -0
  17. package/skills/neocompose-cli/references/neoscript/control-flow.md +123 -0
  18. package/skills/neocompose-cli/references/neoscript/delegates-and-listeners.md +107 -0
  19. package/skills/neocompose-cli/references/neoscript/math-and-random.md +114 -0
  20. package/skills/neocompose-cli/references/neoscript/neoscript.md +113 -0
  21. package/skills/neocompose-cli/references/neoscript/strings.md +47 -0
  22. package/skills/neocompose-cli/references/neoscript/testing-and-migrations.md +73 -0
  23. package/skills/neocompose-cli/references/neoscript/types-and-narrowing.md +111 -0
  24. package/skills/neocompose-cli/references/sync/branches-and-history.md +69 -0
  25. package/skills/neocompose-cli/references/sync/conflicts-and-recovery.md +68 -0
  26. package/skills/neocompose-cli/references/sync/sync.md +96 -0
  27. package/skills/neocompose-cli/references/sync/unity-export.md +63 -0
  28. package/skills/neocompose-cli/references/values/defaults-and-localization.md +86 -0
  29. package/skills/neocompose-cli/references/values/files.md +71 -0
  30. package/skills/neocompose-cli/references/values/identities.md +97 -0
  31. package/skills/neocompose-cli/references/values/references.md +151 -0
  32. package/skills/neocompose-cli/references/values/values.md +110 -0
  33. package/skills/neocompose-cli/references/world/animation.md +133 -0
  34. package/skills/neocompose-cli/references/world/grids-and-cell-patterns.md +55 -0
  35. package/skills/neocompose-cli/references/world/variants.md +112 -0
  36. package/skills/neocompose-cli/references/world/world.md +105 -0
  37. package/skills/neocompose-cli/references/animation-and-world-authoring.md +0 -582
  38. package/skills/neocompose-cli/references/commands-and-sync.md +0 -408
  39. package/skills/neocompose-cli/references/declarations-and-construction.md +0 -773
  40. package/skills/neocompose-cli/references/neoscript.md +0 -990
  41. package/skills/neocompose-cli/references/values-identities-and-references.md +0 -421
@@ -1,990 +0,0 @@
1
- # NeoScript
2
-
3
- Use this reference for inline code, snippets, nullability, control flow,
4
- runtime ownership, evaluation, and migrations.
5
-
6
- ## Contents
7
-
8
- - Inline bodies and snippets
9
- - Nullability and ownership
10
- - Tuples and destructuring
11
- - References
12
- - Overloaded calls
13
- - Numeric builtins
14
- - Collection builtins
15
- - Conditional and logical operators
16
- - Loops and transfer
17
- - Switch
18
- - Try/catch
19
- - Debug output
20
- - Triggering dialogues
21
- - Evaluation and migrations
22
-
23
- ## Inline bodies and snippets
24
-
25
- Keep computed getters, setters, functions, dialogue logic, group logic, and
26
- constructors inline in their owning `.neo` or `.neoflow` declaration. Do not
27
- create `Scripts/<Class>/<Member>.neo` sidecars.
28
-
29
- Explicit constructions such as `new MissingClass(5)` require a declared type,
30
- including inside stored object initializers, constructor arguments, and collections.
31
- The editor reports an unresolved class at its type name and refreshes the diagnostic
32
- when the declaration changes. Contextual `new(...)` still uses the expected type.
33
-
34
- If resolving a stored reference encounters an invalid initializer elsewhere,
35
- the diagnostic points to that initializer's authored file and position. Fix the
36
- reported initializer; a valid downstream `Reference(...)` is not the error site.
37
-
38
- Declare a setter's value type explicitly with the canonical form
39
- `set(int value) { ... }`. Project analysis, candidate materialization, and
40
- focused script commands all use the same accessor extraction. A property can have at most one getter and one setter. Concrete setters need a body (an empty body is a valid no-op); typed setters must use the property type and the parameter name `value`. Abstract and interface properties declare bodyless accessor contracts.
41
-
42
- A body whose whole point is one expression may be written as an expression
43
- body, `=> expr;`. It is pure sugar for the block form — same compiled
44
- instructions, same diagnostics, same stored source — and is available on
45
- getter-only properties, individual accessors, and functions:
46
-
47
- ```neo
48
- public bool HasVisited => this.VisitCount > 0;
49
-
50
- public int Clamped {
51
- get => Math.Clamp(this.Raw, 0, 10);
52
- set(int value) { this.Raw = value; }
53
- }
54
-
55
- public bool Unlocked(int level) => this.Level >= level;
56
- ```
57
-
58
- The `=>` is only an expression body where it directly follows the member name
59
- or the parameter list's `)`. An arrow after an `=` initializer is always a
60
- NeoFlow destination, so `override Trigger Trigger = new(...) => Welcome;`
61
- keeps its meaning.
62
-
63
- In a void context — a `void` function, a setter, or a void delegate — `=> expr`
64
- is an expression statement rather than a return, so the expression must do
65
- something: a call such as `void Ping() => this.OnUpdated();`, or an assignment
66
- such as `set(int value) => this.Raw = value;`. An expression whose value would
67
- simply be discarded is rejected with a diagnostic that names the block body as
68
- the fix.
69
-
70
- `neo pull` and canonical emission write a body as the shorthand when it is
71
- exactly one `return` statement that fits on one line, and anything else as a
72
- block. Setters are always blocks. `neo push` keeps the body form you authored,
73
- so choosing longhand for a one-line getter is stable.
74
-
75
- Properties require a getter implementation. A bodyless `{ get; }` contract
76
- requires `abstract` on the member, even inside an abstract class, for example
77
- `public abstract int Count { get; }`. For stored data, declare a field such as
78
- `public int Count = 0;`. VSCode and the CLI diagnose a missing getter body at
79
- the property name. Interface property contracts remain bodyless.
80
-
81
- Declare a bodyless NeoScript contract with `abstract`, for example
82
- `public abstract bool Equals(Item other);`. Reserve `native` for host-provided
83
- functions; concrete NeoScript overrides inherit the abstract signature.
84
-
85
- Inside an override body, use `base.Member(args)` or `base.Property` to invoke
86
- its nearest ancestor implementation. The ancestor receives the same `this`,
87
- so ordinary virtual calls inside that implementation still reach the most
88
- derived override. `base` outside an override or targeting an abstract member
89
- is a compile error. Base constructors and stored-field reads are not supported.
90
-
91
- Use the shared language service through focused commands:
92
-
93
- ```sh
94
- neo script check --all
95
- neo script check --this Outpost --returns string 'return $"{this.Name}!";'
96
- neo script check --mode setter --member ComputedName 'root.Session.Name = value;'
97
- neo script check --mode nsfunction --member Outpost.RefreshUnlock 'return this.Level > 0;'
98
- neo script compile --mode nsfunction --member Outpost.RefreshUnlock 'return this.Level > 0;'
99
- neo script eval --returns string 'return root.Assets.Outposts[0].FullDisplayText;'
100
- neo script eval --function Outpost.RefreshUnlock --this-value <id> --args '[3]'
101
- ```
102
-
103
- Method calls may be used as statements even when they return a value. The
104
- runtime evaluates the call and discards the result. This also applies to
105
- collection methods, delegates, actions, and fluent calls:
106
-
107
- ```neo
108
- this.TryRefresh();
109
- this.Items.Insert(0, item);
110
- this.Items.Remove(item);
111
- ```
112
-
113
- A generic receiver may call methods from its class or interface constraint,
114
- including void calls used as statements. Generic arguments substitute through
115
- the constraint; nullable receivers still require narrowing or null handling.
116
-
117
- Runtime `is` tests on generic values check whether the bound can overlap the
118
- tested type. A class bound need not implement an interface itself when a
119
- subclass can implement it. A successful pattern binding exposes that interface's
120
- members, including after `if (item.Data is not IReceiver receiver) continue;`.
121
- Sealed classes without the interface and unrelated class bounds remain disjoint.
122
-
123
- An interface guard also narrows the original receiver without a pattern binding.
124
- After `if (item is not IReceiver) continue;`, `item` exposes both its declared
125
- class members and `IReceiver` members. The same applies to guards that return,
126
- throw, or break. Reassignment clears the proof, and paths that did not pass the
127
- guard cannot use it.
128
-
129
- A class can construct each interface only once. A class value known to implement
130
- `IBox<int>` cannot pass an `is IBox<string>` check, including through inherited
131
- interfaces. Open classes can still acquire an unrelated interface. Checks between
132
- interface-only receivers retain their runtime behavior.
133
-
134
- Guards preserve a generic receiver's `T` identity and its declared bound. A
135
- declaration pattern also keeps the original receiver's members: after
136
- `if (item is not IReceiver receiver) return;`, `item` still has its class or
137
- generic type, while `receiver` exposes `IReceiver`.
138
-
139
- A later class guard retains these capabilities. For example, after checking
140
- `item is IReceiver` and then `item is Furnace`, the original `item` exposes both
141
- `Furnace` and `IReceiver` members. A sealed class that cannot implement the
142
- proven interface remains an impossible test.
143
-
144
- Guards also narrow named global values and static member paths. Qualified and
145
- unqualified static access share the same proof, including inherited statics.
146
- Assignments clear affected proofs. A closure must establish its own guard
147
- because the stored value can change before the closure runs.
148
-
149
- Functions, natives and interface functions can declare their own type
150
- parameters after the name, each with an optional constraint and default:
151
-
152
- ```neo
153
- public static T? FirstOf<T extends Item = Item>(List<T> items) => items.FirstOrDefault();
154
- ```
155
-
156
- A call writes every type argument or omits the defaulted tail
157
- (`FirstOf<Seed>(items)`, `FirstOf(items)`). When `<...>` is omitted, supplied
158
- arguments infer the function's type arguments. Lambda inputs bind first so their
159
- returns can infer output parameters, as in `Map(seed, (value) => value.Price)`.
160
- Defaults fill parameters with no inference evidence. The destination/expected
161
- return type does not provide evidence. Explicit `<...>` keeps explicit/default
162
- binding and does not infer omitted trailing arguments. Conflicting types require
163
- explicit arguments; inference does not invent a shared ancestor for siblings. Function type
164
- parameters take no `@id`, and an override declares the same count, constraints
165
- and defaults, though it may rename them.
166
-
167
- Prefer `--json` for automation.
168
-
169
- `neo script check --all` reconstructs the prospective project entirely from
170
- the local workspace. It checks local schema bodies and stored dialogue,
171
- migration, and text-variable bodies against that same source transaction, so
172
- new declarations do not depend on a prior push or a network fetch.
173
-
174
- Inside an inline Class body, unqualified names resolve against that Class;
175
- invoke delegate and action members directly with `Selector()` or `OnUpdated()`.
176
- Spell a zero-argument action as bare `NeoAction`; type arguments name action
177
- parameters, so `NeoAction<void>` is invalid rather than a zero-argument alias.
178
- Clear every subscribed listener from a stored action with
179
- `this.OnUpdated.Clear()`. `Clear()` is a mutating statement and follows the
180
- action member's effective storage and writability. Only the action's declaring
181
- class and derived classes may invoke it or call `Clear()`; public external
182
- callers may subscribe and unsubscribe with `+=` and `-=` but cannot raise or
183
- reset the action.
184
-
185
- Inline `NeoDelegate` lambdas may be returned, stored, or assigned to a typed
186
- local such as `NeoDelegate<int, int> twice = (x) => x * 2;`, and may read
187
- surrounding locals and parameters. Those values are captured when the lambda is
188
- created, so later changes to the outer binding do not alter the closure.
189
- Any non-null delegate expression can be called directly, including
190
- `callbacks[0](value)`, `(condition ? first : second)()`, and `Factory.Make()()`.
191
- Force-unwrap a nullable delegate before calling it, as in `callbacks["key"]!()`.
192
- Void delegates can only be called as statements.
193
- Instance delegate field defaults are compiled against their declared signature.
194
- Each instance binds defaults that read `this` to itself; invalid lambda bodies
195
- are reported at the declaration by status, editor validation, and preparation.
196
- Static delegate fields use static class scope: they can read static members
197
- without an instance owner, and cannot access `this` or instance members.
198
- Field initializers can read their declaring class's static members without a
199
- qualifier, including inherited statics and reads inside nested object defaults:
200
- `public int First = Run();` and `public static int Copy = Number;`.
201
- Eager initializers cannot read instance members or `this`; static defaults also
202
- cannot capture class-header parameters. Defer instance reads inside a delegate
203
- lambda. A delegate field default may also name a method group bare.
204
- `Value = Read` or `this.Read` binds each new instance to its own `Read`, and a
205
- static function may be named as `Read` or qualified as `Holder.Read`/`Other.Read`.
206
- A qualified method group must name a static function. Method groups use the same
207
- signature rule as in a body, and a mismatch is reported at the declaration.
208
- A static field that names another static copies a value, as in
209
- `public static int B = Holder.A;`. A class, List, Set, or Dictionary static
210
- already owns its value, so call `Clone()` or use `Reference`. A static delegate cannot copy another Class's delegate field, including an inherited one,
211
- because its closure compiles in its own Class; wrap it as `() => Other.Call()`.
212
- Omitted constructor fields use their authored defaults, including optional fields.
213
- Writable collection defaults belong to each instance; mutations persist on later
214
- reads without changing another instance's default. Fields without defaults remain
215
- absent, and `null` arguments in generated member-wise constructors mean omission.
216
- Collection type arguments in constructions, such as `new Box<List<int>>([1, 2])`,
217
- work even when no field declares that collection type. Preparation includes the
218
- needed type descriptors; spec-only types remain in the test candidate.
219
- Generic primary constructors also accept stored collection arguments, such as
220
- `public static Box<List<int>> Value = new([1, 2]);`. Stored replay uses the closed
221
- parameter type to preserve collection references.
222
- Stored constructors validate every authored type argument, including names nested
223
- inside collections and generic types. An expected field type cannot hide an unknown
224
- type in `new Box<List<MissingType>>()`.
225
- Typed locals also accept generic class types such as `Box<int> box = new(1);`.
226
- Generic class arguments remain invariant through inheritance: `IntBox : Box<int>`
227
- can be assigned to `Box<int>`, but not `Box<string>`. Arguments are substituted
228
- through every intermediate base class, including nested generic arguments.
229
- Explicit lambda parameter annotations accept the same type grammar as
230
- declarations, including `decimal`, generic parameters, collections, arrays, and
231
- nullable types.
232
-
233
- Instance reads in collection-entry initializers must be deferred. A default such
234
- as `List<int> Values = [this.Number + 1]` (or `[Number + 1]`) is a compile error:
235
- the instance does not exist while the entry initializer runs. Delegate lambdas
236
- such as `() => this.Number` and method groups such as `Read` or `this.Read`
237
- remain valid, including inside `new Callback { Read = Read }` entries.
238
-
239
- A lambda may use an expression body too: `(x) => x * 2` is the block body
240
- `(x) => { return x * 2; }`. In a void delegate the expression is a statement,
241
- so it must call or assign. A dictionary-literal body needs parentheses,
242
- `() => ({ "a": 1 })`, because a bare `{` after the arrow always opens a block.
243
-
244
- ```neo
245
- List<Item> ready = this.Items.Where((item) => item.Count > 0);
246
- ```
247
-
248
- `==`, `!=`, `Contains`, `IndexOf`, and `Remove` compare Class, interface,
249
- List, Set, and Dictionary values by identity: two reads of one stored value are
250
- equal, and two values with identical fields are not. Primitives, enums,
251
- vectors, and media references compare by value. The static type decides, so an
252
- unconstrained generic `T` compares by value.
253
-
254
- Calling `Equals(other)` on a non-null generic value dynamically uses the
255
- runtime Class's one-argument `Equals` Function or NSFunction when it returns
256
- `bool`, including the effective override. Values without that member fall back
257
- to value equality. For local sets, that fallback compares their current entries
258
- in insertion order, regardless of prior reads or mutations.
259
-
260
- Object initializers are available in method bodies and `.spec.neo` tests:
261
-
262
- ```neo
263
- var tile = new DirtWetTile { Cell = Vector2Int(3, -2) };
264
- ```
265
-
266
- An entry that settles a required member is evaluated with construction, as the
267
- constructor argument it stands in for. The remaining entries then use ordinary
268
- assignment rules in source order, including property setters, access checks,
269
- and readonly restrictions. Initializer expressions read the caller's scope.
270
-
271
- ## Nullability and ownership
272
-
273
- Do not access a nullable receiver directly. First narrow it with a condition, or
274
- use null propagation `?.`, coalescing `??`, or explicit force unwrap `!` when
275
- failure is intended:
276
-
277
- ```neo
278
- if (player.Target != null) {
279
- return player.Target.Name;
280
- }
281
- return player.Target?.Name ?? "Unknown";
282
- ```
283
-
284
- Null propagation remains active across following required accesses in the same
285
- chain, so `config.Hat?.Head.Idle.Down.Frames` safely returns null when `Hat` is
286
- null. A descendant that is itself nullable still needs its own `?.`, `??`, `!`,
287
- or narrowing guard before the next access.
288
-
289
- Inside an instance body, `Member` and `this.Member` share null and type-check
290
- narrowing. A local or parameter named `Member` shadows the implicit member;
291
- `this.Member` still names the instance field. Reassigning a narrowed path to a
292
- nullable value clears its non-null fact and facts about its descendants. Writes to
293
- unrelated members preserve existing guards, including inside loops. Earlier
294
- conditions being false also narrow receivers in later `else if` conditions.
295
-
296
- A nullable value coalesced with a non-null fallback produces a non-null result,
297
- including in constructor arguments: `new Sprite(item.Sprite ?? SpriteInfo.Empty)`.
298
- A nullable fallback keeps the result nullable when the left operand can be null.
299
- When a reference result is expected, `first ?? fallback` selects the reference
300
- itself. A present reference to a null target does not select the fallback.
301
- In a scalar context, references still read their targets before coalescing.
302
-
303
- Treat lookup-return values with the same nullable narrowing rules.
304
-
305
- Runtime declaration patterns follow the possible runtime type, not only the
306
- static declaration. An open Class value may match an otherwise unrelated
307
- interface through a derived Class that implements it; a sealed Class with no
308
- such relationship remains a compile-time impossible pattern.
309
-
310
- A declaration pattern introduced by `is` stays available after a guard when
311
- every path that falls through matched the pattern, including when the guard's
312
- other paths return from a nested block:
313
-
314
- ```neo
315
- if (item is not BedItem bedItem) {
316
- throw "Expected a bed item";
317
- }
318
- return bedItem.Name;
319
- ```
320
-
321
- `value is not SomeClass` negates a type test. An optional binding, as in
322
- `value is not SomeClass item`, exists only where the test is known to be false:
323
- the `else` branch, the right side of `||`, or after a rejecting guard. The
324
- operand is evaluated once. `not` remains a normal identifier elsewhere.
325
-
326
- Use `SpriteInfo.Empty` when a required sprite should intentionally render no
327
- image. It is the canonical non-null empty sprite: it has no backing file and
328
- uses slice index zero.
329
-
330
- Let runtime ownership—not body kind—decide whether a write target is Immutable,
331
- Save, Session, Setter, or otherwise writable. A constructor writes only its
332
- `this` instance. A writable structured-leaf field still inherits the receiver's
333
- effective storage restrictions.
334
-
335
- ## Tuples and destructuring
336
-
337
- A tuple has at least two ordered elements. Labels are optional and explicit;
338
- `Item1`, `Item2`, and later positional aliases always work. Labels do not affect
339
- type identity. Slots are immutable, so replace a whole tuple to change a value.
340
- Referenced objects retain their normal permissions and identity.
341
-
342
- ```neo
343
- (int Row, int Col) position = (Row: 2, Col: 3);
344
- var (row, col) = position;
345
- (int first, (bool enabled, int _)) = (1, (true, 9));
346
- var score = positions.Select(((r, c)) => r + c);
347
- foreach (var (key, value) in weights) { Debug.Log(key); }
348
- ```
349
-
350
- `((r, c)) =>` accepts one tuple; `(r, c) =>` accepts two parameters. Recursive
351
- patterns work in declarations, loop bindings, and tuple lambda parameters. `_`
352
- discards a position. Assignment deconstruction is not supported.
353
-
354
- Tuple equality compares corresponding values recursively. Decimal scale does
355
- not affect equality. Classes, collections, and explicit references compare by
356
- identity. `Contains`, `IndexOf`, `Remove`, and `ToSet` use the same rule.
357
- Nullable tuples use `(int, int)?`; nullable elements use `(int?, int)`.
358
-
359
- ## References
360
-
361
- `Reference<T>` names one stored row. `T` can be any value type, including
362
- collections and other references. Code types such as `Action<int>` are
363
- rejected. `T` picks one of two categories:
364
-
365
- - A handle is a class, interface, list, set, or dictionary. The reference is the
366
- object itself, so `Reference<Item>` reads as `Item`. Assigning an `Item`
367
- rebinds it.
368
- - A slot is any other value type: `bool`, numbers, `string`, enums, vectors,
369
- `Color`, sprites, audio clips, `NeoVariant<T>`, `NeoDialogueReference`, or a
370
- single `Reference<U>`. The reference names another member and reads and writes
371
- that member's value. It converts to its `T` by reading.
372
-
373
- `Reference(place)` forms a reference to a stored member, such as `this.Gold`,
374
- `root.Player.Gold`, `Npc.Mood`, or `item.Count`. Its type comes from the
375
- member's declaration. When `Selected` is `Reference<Item>`,
376
- `Reference(this.Selected)` is `Reference<Reference<Item>>` and names the
377
- selection slot itself. Locals, parameters, list or dictionary elements, vector
378
- components, getters, query results, and new values have no row to reference.
379
- Access checks apply where the reference is formed.
380
-
381
- Assigning to a reference rebinds it when the right side is a reference of the
382
- same type. Otherwise the value is written into the target:
383
-
384
- ```neo
385
- this.A = 5; // writes 5 into A's target
386
- this.A = this.Count; // writes Count's value into A's target
387
- this.A = this.B; // A now names B's target
388
- this.A = Reference(this.Count); // A now names Count
389
- this.A += 1; // reads A's target, writes it back
390
- ```
391
-
392
- Conditional assignments such as `this.A = choose ? first : second` rebind `A`
393
- when both branches supply references. Scalar branches write through `A`; a mixed
394
- reference and scalar choice reads the selected reference's target. Constructors
395
- may initialize their own reference fields this way, but cannot write through
396
- external targets.
397
-
398
- A plain value never becomes a reference: passing `3` to a `Reference<int>`
399
- parameter is an error. A handle reference never owns, so it can't hold a new
400
- list or a query result.
401
-
402
- `==` compares handles by identity and slots by value. A collection of
403
- references compares entries by target, so a `Set<Reference<int>>` holds
404
- distinct slots. Narrow a `Reference<T>?` with a null check before writing
405
- through it.
406
-
407
- The target row decides every write. Writing an asset-owned target errors, and a
408
- `readonly` target can't be written after initialization. A slot reference's
409
- `OnChanged` listeners fire when it rebinds and when its target's value changes.
410
- Inside a generic body, `Reference<T>` takes the category of `T`'s binding.
411
-
412
- ## Overloaded calls
413
-
414
- A call to an overloaded function picks one overload at compile time from its
415
- arguments. Candidates are every overload the receiver's type sees, across all
416
- inheritance levels; a type qualifier takes static ones, an instance receiver
417
- instance ones. An overload applies when the argument count fits its required
418
- and default parameters and each argument converts to its parameter. Lambdas,
419
- method groups, `null`, collection literals, and `.North` apply by shape.
420
-
421
- Among applicable overloads, the one whose parameters are closer to the
422
- argument types wins: an exact type beats a conversion, and `Bag` beats `Item`
423
- when `Bag : Item`. On a tie, the overload filling no defaults wins, then the
424
- non-generic one. Otherwise the call is ambiguous: `Scale(float)` and
425
- `Scale(decimal)` can't take an `int`, so write `2.0` or `2m`. A generic
426
- overload takes explicit type arguments, or infers them from the arguments the
427
- way a lone generic call does, with defaults filling parameters nothing infers.
428
- An overload whose type arguments can't be inferred isn't a candidate: beside
429
- `F(int a, string b)`, `F<T>(T a, T b)` takes `F(1, 2)` but not `F(1, "a")`.
430
-
431
- A method group picks the overload whose parameters match the target delegate
432
- type exactly, in assignments, `+=`, `OnChanged`, `@listeners`, and delegate
433
- arguments. `var f = Read` is an error when `Read` is overloaded; give `f` a
434
- delegate type.
435
-
436
- Pass the delegate type to mock or spy on an overloaded function:
437
-
438
- ```neo
439
- var quote = mock.function<NeoDelegate<int, Item, int>>(Pricing.Quote);
440
- ```
441
-
442
- Script evaluation selects an overload with a signature:
443
- `neo script eval --function "Inventory.Add(Item, int)"`.
444
-
445
- ## Numeric types
446
-
447
- `int` stores whole numbers and division truncates toward zero. Ordinary integer
448
- arithmetic retains the precision of the stored number. `float` literals, stored
449
- values, and every float arithmetic step round to 32 bits, matching Unity.
450
- `decimal` is exact base-10 and is the only type to use for currency or other
451
- values that must not drift.
452
-
453
- ## Bitwise integer operators
454
-
455
- Use `~`, `&`, `^`, `|`, `<<`, `>>`, and `>>>` with non-nullable `int` operands.
456
- The compound forms are `&=`, `^=`, `|=`, `<<=`, `>>=`, and `>>>=`.
457
- Enums, bools, float literals such as `1.0`, and decimals are not accepted.
458
- Use `Set<Enum>` for enum membership.
459
-
460
- ```neo
461
- int mask = (region << 8) | (tile & 255);
462
- bool hasEast = (mask & 2) != 0;
463
- mask &= ~2;
464
- ```
465
-
466
- Each operand wraps modulo 2^32 into signed int32 at the bitwise boundary.
467
- Every result is signed int32. Ordinary integer arithmetic is unchanged.
468
- Shift counts use their low five bits: 32 means 0, 33 means 1, and -1 means 31.
469
- `>>` extends the sign bit; `>>>` fills with zero but still returns a signed
470
- result. Thus `-1 >>> 1` is 2147483647 and `-1 >>> 0` is -1.
471
- Operands evaluate once, left to right. A compound assignment captures its
472
- target and reads its old value before evaluating the right operand.
473
-
474
- Precedence from tightest to loosest: unary, switch expressions, multiplication/division/remainder,
475
- addition/subtraction, shifts, comparisons, equality and `is`, `&`, `^`, `|`,
476
- `&&`, `||`, `??`, then `?:`. Binary bitwise operators associate left to right.
477
- Keep shift characters contiguous: `x >> 1` is valid; `x > > 1` is not.
478
-
479
- ## Numeric builtins
480
-
481
- Use the `Math` namespace for numeric utilities with C# names and C# semantics:
482
- `Min`, `Max`, `Clamp`, `Round`, `Floor`, `Ceiling`, `Truncate`, `Abs`, `Sign`,
483
- `Sqrt`, and the `PI` and `E` constants.
484
-
485
- ```neo
486
- return Math.Clamp(this.Quality + bonus, 0, 10);
487
- ```
488
-
489
- Each result follows the arithmetic join of its argument types: all-`int` gives
490
- `int`, any `float` gives `float`, any `decimal` gives `decimal`, and `decimal`
491
- never mixes with `float`. `Round`, `Floor`, `Ceiling`, and `Truncate` are the
492
- language's `float`-to-`int` conversions: they return `int` for `int` and
493
- `float` input, keep a `decimal` a `decimal`, and reject a non-finite argument
494
- at runtime. `Round` rounds midpoints to even, matching `decimal.Round(0)`.
495
- `Sign` always returns `int`; `Sqrt` takes `int` or `float` only.
496
-
497
- `Math` is an unqualified builtin qualifier, never a type or value, so no Class,
498
- enum, parameter, or local may shadow it. Qualified members use their owner's
499
- namespace and may be named `Math`; access one as `this.Math` or
500
- `SomeValue.Math` without colliding with the builtin qualifier.
501
-
502
- ## Vector math
503
-
504
- `Vector2`, `Vector3`, `Vector2Int`, and `Vector3Int` support `+`, `-`, unary
505
- `-`, multiplication by a scalar in either order, and division by a scalar.
506
- Both vector operands must have the same type. Integer vectors accept only
507
- integer scalars; float vectors accept int or float. Integer division truncates
508
- toward zero. Division by zero throws. `==` and `!=` keep exact component equality.
509
-
510
- ```neo
511
- Vector2 next = position + direction * speed;
512
- float distance = Vector2.Distance(position, target);
513
- Vector2 heading = (target - position).normalized;
514
- Vector2 step = Vector2.MoveTowards(position, target, speed);
515
- ```
516
-
517
- All vectors expose read-only `magnitude` and `sqrMagnitude`, static `Distance`,
518
- `Min`, `Max`, and constants `zero`, `one`, `up`, `down`, `left`, and `right`.
519
- Three-dimensional vectors also have `forward` and `back`. Float vectors add
520
- `normalized`, `Dot`, `Lerp`, and `MoveTowards`; `Vector3` adds `Cross`.
521
- Integer vectors have no implicit conversion to float vectors.
522
-
523
- `Color.Lerp(a, b, t)` interpolates all four channels, including alpha.
524
- Both vector and Color `Lerp` clamp t to [0, 1]. `normalized` returns zero when
525
- the magnitude is at most 0.00001. A negative MoveTowards delta moves away.
526
- Computed properties are read-only; arithmetic, math results, and constants
527
- are fresh values. Component writes retain the ordinary vector aliasing rules.
528
-
529
- ## String builtins
530
-
531
- Use `text.Replace(search, replacement)` to return a string with every
532
- non-overlapping occurrence replaced. Matching is ordinal and case-sensitive.
533
- The search must be nonempty; an empty search throws an error. Neither argument
534
- may be null. The receiver stays unchanged.
535
-
536
- ```neo
537
- return "banana".Replace("a", "o"); // "bonono"
538
- ```
539
-
540
- Strings also have `Length`, `Substring(startIndex[, length])`, `IndexOf`,
541
- `LastIndexOf`, `Split(separator)`, `PadLeft`/`PadRight(totalWidth[,
542
- paddingChar])`, `Trim`, `TrimStart`, and `TrimEnd`, plus the statics
543
- `string.Join(separator, values)`, `string.IsNullOrEmpty`, and
544
- `string.IsNullOrWhiteSpace`. Lengths and indices count UTF-16 code units, every
545
- comparison is ordinal, and whitespace is C#'s `char.IsWhiteSpace` set. `Split`
546
- keeps empty entries and returns a new list. `Join` takes a `List<string>` or
547
- `Set<string>`; turn other entries into text first with
548
- `values.Select(v => $"{v}")`. `s?.Length` is an error; use
549
- `string.IsNullOrEmpty(s)` or narrow first.
550
-
551
- ```neo
552
- List<string> parts = this.Text.Trim().Split(" ");
553
- int quantity = int.TryParse(parts[1]) ?? 0;
554
- return $"{slot.ToString("D3")} {price.ToString("N2")}";
555
- ```
556
-
557
- `int.Parse`, `float.Parse`, and `decimal.Parse` accept ASCII digits with an
558
- optional sign (plus a fraction for `float` and `decimal`, and an exponent for
559
- `float`) after trimming ASCII whitespace, and throw on anything else or on a
560
- value the type can't hold. `TryParse` returns `null` instead of throwing.
561
- `decimal.Parse` never rounds: more than 28 significant or fractional digits is
562
- an error.
563
-
564
- Numbers format only with an explicit format: `D<n>` (int only, zero-padded),
565
- `F<n>` (fixed point), or `N<n>` (fixed point with `,` grouping), where `n` is 0
566
- to 28 and defaults to 2 (1 for `D`). `float` rounds half to even from its exact
567
- value, `decimal` rounds half away from zero, and a result that is all zeros has
568
- no `-` sign. For plain text, use interpolation: `$"{value}"`.
569
-
570
- Integer inputs assigned to `float` round to float32. This also applies to
571
- function arguments and returns, collection entries, and `List.Repeat` with a
572
- float entry type. `Contains` and `IndexOf` on float collections also convert
573
- integer search arguments. Nullable assignments preserve `null`.
574
-
575
- ## Collection builtins
576
-
577
- Lists expose their size as either `items.Count` or `items.Count()`. Use
578
- `items.Count((item) => predicate)` to count only matching entries without
579
- materializing a filtered list. The same Count forms work on Dictionary and Set
580
- collections; dictionary predicates may accept `(key, value)` just like the
581
- other collection callbacks.
582
-
583
- Use `Any()` or `Any((item) => predicate)` to check for a match, and
584
- `All((item) => predicate)` to check every entry. Both short-circuit; an empty
585
- collection has `Any() == false` and `All(...) == true`.
586
-
587
- `First`, `FirstOrDefault`, `Last`, `LastOrDefault`, `Single`, and `SingleOrDefault` accept zero arguments
588
- or one predicate. `First` and `Last` throw when no entry matches. Enum sets
589
- return enum values, including when a predicate selects the entry. `Single` throws unless
590
- exactly one matches; `SingleOrDefault` still throws for multiple matches.
591
- The `OrDefault` forms return nullable entries and use `null` for no match.
592
-
593
- Build eager, read-only list results with `SelectMany((item) => item.Children)`,
594
- `OrderBy((item) => item.Name)`, `OrderByDescending((item) => item.Score)`,
595
- `Skip(count)`, `Take(count)`, `SkipWhile((item) => predicate)`,
596
- `TakeWhile((item) => predicate)`, `Reverse()`, and `Concat(other)`.
597
- `SelectMany` requires a non-null list or set from its selector. Sorting is
598
- stable, evaluates each key once, uses ordinal string comparison, and accepts
599
- bool, int, float, decimal, or string keys, including nullable keys. Null sorts
600
- first ascending and last descending. Negative skip/take counts
601
- behave as zero. Reference entries keep their identity and storage permissions;
602
- a read-only result does not grant additional write access to its entries.
603
-
604
- ```neo
605
- return this.Beds
606
- .SelectMany((bed) => bed.Plants)
607
- .Where((plant) => plant.Ready)
608
- .OrderBy((plant) => plant.Name)
609
- .Take(10);
610
- ```
611
-
612
- `Sum`, `Average`, `Min`, and `Max` accept zero arguments or one selector.
613
- Sum/Average require int, float, or decimal values; Min/Max also accept strings
614
- and bools. Null values are skipped. Sum returns zero on empty input; the other
615
- reductions return null for nullable input and throw for required input.
616
- Average of ints returns float. Decimal calculations stay exact; decimal Average
617
- rounds half-even within Neo's 28-digit envelope. Float Min stops at the first
618
- NaN; Max prefers ordinary values to NaN.
619
-
620
- `MinBy((item) => item.Score)` and `MaxBy(...)` return the first entry with the
621
- winning key. They skip null keys unless every key is null, then keep the first
622
- entry. Empty input throws unless the entry type is nullable.
623
-
624
- List and Set support `Aggregate((acc, item) => ...)`,
625
- `Aggregate(seed, (acc, item) => ...)`, `Distinct()`, and
626
- `Zip(other, (left, right) => ...)`. Aggregate visits entries in order; an empty
627
- unseeded fold throws. Distinct retains first occurrences using Contains equality
628
- and returns a read-only List, including nullable entries. Zip stops at the
629
- shorter input.
630
-
631
- `GroupBy((item) => item.Category)` returns a read-only dictionary of read-only
632
- lists. `ToDictionary((item) => item.Name)` optionally accepts a second value
633
- selector. Keys must be non-null strings or enum values; duplicate dictionary
634
- keys throw. Dictionary callbacks take `(key, value)`. `.Keys` and `.Values`
635
- return read-only lists in dictionary traversal order (integer-looking string
636
- keys precede other strings in numeric order).
637
-
638
- Chain `ThenBy` or `ThenByDescending` after OrderBy to add stable secondary keys.
639
- An inferred `var` retains this capability; ordinary List/Set annotations,
640
- function boundaries, and non-ordering queries erase it. Every ordering call is
641
- eager: it caches keys and sorts the snapshot, costing O(n log n * k) for k key
642
- levels. Each selector runs once per entry per level. Mutating a snapshot through an explicitly writable local alias invalidates its cached keys; ThenBy then throws until a new OrderBy establishes an ordering.
643
-
644
- Writable ordered lists support `items.Sort()` for scalar entries or
645
- `items.Sort((left, right) => left.Score - right.Score)` for an int comparison.
646
- Sort returns void, preserves entry identity, and is stable. It rejects mutation
647
- of the target list through aliases while comparisons run; entry fields retain
648
- their existing write permissions. A failed comparison leaves the order unchanged.
649
-
650
- Use `items.IndexOf(value)` to return the zero-based index of the first equal
651
- list entry, or `-1` when the value is absent. `IndexOf` is list-only and uses
652
- the same equality as `Contains`.
653
- `Remove(value)` removes the first equal entry using that same equality, including
654
- scalar entries supplied by instance field defaults. Removing a missing value is a no-op.
655
-
656
- Use `items.Insert(index, value)` to insert an entry into a writable ordered
657
- list. The index may range from zero through `items.Count`; the count appends
658
- the entry. Other indices throw an error. `Insert` returns void.
659
-
660
- Use `List.Repeat(value, count)` to build a list of `count` copies of a value
661
- instead of hand-counting a literal or looping around `Add`:
662
-
663
- ```neo
664
- List<int> scores = List.Repeat(0, 10);
665
- ```
666
-
667
- The entry type is normally inferred from the value and joined with the
668
- assignment context, exactly like a list literal, so assigning
669
- `List.Repeat(0, 4)` to a `List<float>` widens the entries to `float`. When the
670
- value cannot carry the type by itself, use a call-site type argument, for
671
- example `List.Repeat<InventoryItemStack?>(null, slotCount)`. Never spell type
672
- arguments on the qualifier: `List<int>.Repeat(0, 3)` is an error, and bare
673
- `List` is a qualifier, not a value.
674
-
675
- `count` must be an `int` — a `float` count is a compile error, and a negative
676
- count fails at runtime with `List.Repeat count must be non-negative; got
677
- {count}.`. A count of `0` produces an empty `List<T>` whose entry type is
678
- still known. The value is evaluated exactly once, then the count exactly once,
679
- so side effects happen once regardless of `count`, and every entry is that one
680
- value: for a reference-typed entry the same reference repeats `count` times.
681
-
682
- ## Conditional and logical operators
683
-
684
- Use `condition ? whenTrue : whenFalse` for a value chosen by a condition:
685
-
686
- ```neo
687
- T modified = this.Modifier != null ? this.Modifier(value) : value;
688
- ```
689
-
690
- The operator has lower precedence than `??`, `||`, and `&&`, associates from
691
- the right, and evaluates only the selected result arm. Both arms must share a
692
- common assignable type or fit the surrounding expected type.
693
-
694
- Use `&&` and `||` with C# precedence: `&&` binds tighter than `||`, and
695
- parentheses preserve explicit grouping. Both operators short-circuit, so the
696
- right operand is evaluated only when it can change the result. Chained
697
- expressions retain every operand; `a || b || c` never skips `b`.
698
-
699
- ## Loops and transfer
700
-
701
- Use C#-shaped loops:
702
-
703
- ```neo
704
- for (int i = 0; i < Items.Count; i++) {
705
- if (Items[i].Disabled) {
706
- continue;
707
- }
708
- Visit(Items[i]);
709
- }
710
-
711
- foreach (var item in root.Save.Items) {
712
- item.Enabled = true;
713
- }
714
- ```
715
-
716
- A `foreach` receiver must be non-null. It snapshots membership/order once at
717
- entry, so adding/removing collection rows does not alter the scheduled
718
- iterations. `foreach (var value in dictionary)` binds each value. To read keys
719
- as well, deconstruct a dictionary entry:
720
-
721
- ```neo
722
- foreach (var (key, value) in this.Damage) {
723
- if (key == ToolType.Pickaxe) {
724
- ApplyDamage(value);
725
- }
726
- }
727
-
728
- foreach ((ToolType key, float value) in this.Damage) {
729
- ApplyDamage(value);
730
- }
731
- ```
732
-
733
- Deconstruction accepts a dictionary local or member path. Assign a computed
734
- receiver to a local first. It snapshots keys once at loop entry and reads each
735
- value through `dictionary[key]`; removing a key follows the normal missing-key
736
- read behavior. Both bindings are read-only, although writes through a writable
737
- entry remain valid. Enum-keyed dictionaries yield enum keys, including in
738
- collection lambdas such as `Where((key, value) => key == ToolType.Pickaxe)`
739
- and `Select((key, value) => key)`. String-keyed dictionaries yield strings.
740
-
741
- Use `break` for the nearest loop or switch and `continue` for the nearest
742
- loop. A `continue` inside a switch nested in a loop continues the loop.
743
-
744
- A `while` loop checks its boolean condition before each iteration. A
745
- `do / while` loop runs its body once before checking the condition. Both
746
- require braces; the `do / while` condition ends with a semicolon.
747
-
748
- ```neo
749
- while (this.HasWork()) {
750
- this.ProcessNext();
751
- }
752
-
753
- do {
754
- this.ProcessNext();
755
- } while (this.HasWork());
756
- ```
757
-
758
- `continue` checks the condition again in both loops.
759
-
760
- ## Switch
761
-
762
- Use scalar, enum, or nullable selectors with compile-time constant labels:
763
-
764
- ```neo
765
- switch (direction) {
766
- case Direction.North:
767
- Move(0, 1);
768
- break;
769
- case Direction.East:
770
- case Direction.West:
771
- Stop();
772
- break;
773
- default:
774
- throw $"Unknown direction: {direction}";
775
- }
776
- ```
777
-
778
- Permit `int`, `string`, `bool`, enum, and nullable forms. Stack labels when
779
- they share one body. End every reachable non-empty section with `break`,
780
- `return`, `throw`, or `continue` when a surrounding loop makes it valid.
781
- Do not use implicit fallthrough, pattern cases, guards, or `goto` in statement switches.
782
-
783
- ### Switch expressions
784
-
785
- Use `value switch { pattern => result, ... }` to produce one value. Arms may
786
- use scalar/enum constants, `null`, type patterns (`Damage`), declaration
787
- patterns (`Damage damage`), or `_`. A declaration binding is scoped to its
788
- arm's optional `when` guard and result. Patterns and guards run in order; the
789
- selector runs once and only the chosen result runs.
790
-
791
- ```neo
792
- closed class HitResult {}
793
- sealed class Miss : HitResult {}
794
- sealed class Damage : HitResult { public int Amount = 0; }
795
- class Combat {
796
- public string Describe(HitResult result) => result switch {
797
- Miss => "miss",
798
- Damage damage when damage.Amount > 10 => "strong hit",
799
- Damage damage => $"{damage.Amount}",
800
- };
801
- }
802
- ```
803
-
804
- Every switch expression must be exhaustive. Handle every bool/enum option or
805
- closed-class branch, plus null for nullable input. Other types require a
806
- covering type or `_` fallback. A guard contributes no exhaustive coverage
807
- unless it is literally `true`. Missing and unreachable cases are compile
808
- errors. Adding a new subtype recompiles affected consumers.
809
-
810
- Result arms must fit the expected type or have a common assignable type.
811
- Property, relational, list, and positional patterns are unsupported. Pattern
812
- cases in statement switches remain unsupported. Install the matching Unity
813
- SDK before exporting the new switch-expression IR; older SDKs reject it.
814
-
815
- ## Structured errors and finally
816
-
817
- Throw a required string or any `NeoError` subclass. String throws create
818
- `NeoError` values. Each error has a required read-only `Message`, including
819
- an empty message. Derive ordinary classes from `NeoError` to carry typed data.
820
-
821
- ```neo
822
- try {
823
- return GetUnsafe();
824
- }
825
- catch (NeoArgumentError error) when (error.Message != "") {
826
- Report(error.Message);
827
- throw;
828
- }
829
- catch (NeoError error) {
830
- return Fallback();
831
- }
832
- finally {
833
- ReleaseResource();
834
- }
835
- ```
836
-
837
- A try needs catches, a finally block, or both. Catch bindings are read-only.
838
- Typed catches match subclasses, and `catch (string message)` matches every
839
- ordinary error while binding its Message. An unfiltered base catch must follow
840
- all catches it covers. Filters run before any intervening finally blocks,
841
- including across calls. A false or failing filter continues the original search.
842
- Bare `throw;` rethrows the original payload from a catch.
843
-
844
- Finally runs on normal completion, return, break, continue, and ordinary errors.
845
- The return value is evaluated before cleanup. An error escaping cleanup replaces
846
- the pending error; an error caught inside cleanup does not. Return and loop
847
- transfers cannot escape a finally block. A nested loop may break or continue.
848
- Bare throw inside finally needs its own inner catch.
849
-
850
- Runtime failures use `NeoNullReferenceError`, `NeoIndexOutOfRangeError`,
851
- `NeoKeyNotFoundError`, `NeoArgumentError`, `NeoArithmeticError`, or
852
- `NeoInvalidOperationError`. Unexpected native failures become `NeoHostError`
853
- with Message `"Check logs for more details."`. Execution limits, unavailable
854
- preview natives, cancellation, and invalid compiled data are noncatchable.
855
- Suspended Unity calls retain cleanup until resumed; abandonment skips cleanup.
856
- Completed writes are not rolled back. Install the matching SDK for schema 45.
857
-
858
- ## Debug output
859
-
860
- Use `Debug.Log(message)`, `Debug.LogWarning(message)`, and
861
- `Debug.LogError(message)` as void statements. `Debug.Assert(condition, message)`
862
- logs an assertion failure when its non-null bool condition is false. Errors
863
- and failed assertions log without throwing. Both assertion arguments are
864
- evaluated once even when the condition passes; disabling a host sink also
865
- preserves argument evaluation.
866
-
867
- ```neo
868
- Debug.Log($"Loaded {this.Items.Count} items");
869
- Debug.Assert(this.Items.Any(), "Expected at least one item");
870
- ```
871
-
872
- Messages include authored NeoScript line/column locations and stack frames
873
- when source metadata is available. Browser consoles and Unity use the matching
874
- severity; CLI logs go to stderr so JSON stdout stays machine-readable. Getter
875
- re-evaluation may emit the same log again. `Debug` is a builtin qualifier,
876
- not a type; an authored local or Class named `Debug` takes precedence.
877
-
878
- ## Triggering dialogues
879
-
880
- `root.Dialogues` mirrors the generated C# `Dialogues`: the same folders and
881
- groups under their generated C# property names.
882
-
883
- ```neo
884
- public bool TryTalk() {
885
- return root.Dialogues.NPCs.Talk.TryTrigger(this, () => {
886
- this.AfterTalk();
887
- });
888
- }
889
-
890
- public bool HasSomethingToSay => root.Dialogues.NPCs.Talk.CanTrigger(this);
891
- ```
892
-
893
- - `root.Dialogues` has `TryTrigger(string dialogueId, onFinish)`,
894
- `CanTrigger(string dialogueId)`, `VisitCount(pointer)`, and
895
- `HasVisited(pointer)`.
896
- - A standard group has `TryTrigger(onFinish)` and `CanTrigger()`. A lookup
897
- group takes its collection's entry class: `TryTrigger(value, onFinish)` and
898
- `CanTrigger(value)`. The value must be a stored row.
899
- - A `NeoDialogueReference` has `id`, `TryTrigger(onFinish)`, and
900
- `CanTrigger()`. Elements of a `Set<NeoDialogueReference>` are bare ids, so
901
- pass one to `root.Dialogues.TryTrigger`.
902
- - `onFinish` is an optional `NeoDelegate<void>`: a lambda or a function member.
903
- It runs after the dialogue finishes, fails, or is disposed.
904
-
905
- `TryTrigger` compiles only in functions, including effects, lifecycle hooks,
906
- and listeners, and in dialogue actions. `CanTrigger`, `VisitCount`, and
907
- `HasVisited` are reads, allowed wherever the `VisitCount` global is. Folders
908
- and groups aren't values. User code can't trigger dialogues.
909
-
910
- The Unity SDK queues every accepted request and shows one dialogue at a time
911
- through `Dialogues.OnTrigger`. A request for the running or an already queued
912
- dialogue returns false, as does every request when the game has no `OnTrigger`
913
- handler. `neo test`, `neo script eval`, dialogue dry runs, and the web editor
914
- have no dialogue runtime: `TryTrigger` and `CanTrigger` return false there and
915
- `onFinish` never runs.
916
-
917
- Source type names match C#: `class X : NeoDialogue` in `.neoflow`,
918
- `NeoDialogueGroup` and `NeoPriorityGroup` declarations, and
919
- `NeoDialogueReference` for a reference member. The old names `Dialogue`,
920
- `DialogueGroup`, `PriorityGroup`, and `DialogueRef` are compile errors that
921
- name their replacement.
922
-
923
- ## Evaluation and migrations
924
-
925
- `neo script eval` uses authored values and the same evaluator as the web app.
926
- It reports effects from the isolated evaluation but never commits them. Put
927
- persistent NeoScript and data changes in tracked source and use `neo push`.
928
-
929
- Keep migrations as tracked action files under `Migrations/`:
930
-
931
- ```sh
932
- neo migrate new <name> --target <ClassName|project>
933
- neo migrate list
934
- neo migrate check
935
- neo migrate run [--dry-run] [--skip-invalid]
936
- neo migrate prune
937
- ```
938
-
939
- Run checks before migration execution. Review storage ownership and every write
940
- intent; do not assume an action body is writable merely because it compiled.
941
-
942
- `neo migrate run` reads the project from the server and prepares its writes
943
- locally, like push. If another commit lands while it runs, the run is refused:
944
- run it again. `neo loc` writes work the same way: a write refused because
945
- another commit landed is run again.
946
-
947
- VS Code rename follows override families and class constructors, resolves contextual enum options such as `.Success`, renames a constructor parameter's named arguments (`new Item(count: 1)`) in bodies, initializers, specs, and base clauses, and keeps local-variable renames scoped to their declaration. Assigned IDs are not required for these refactorings. Inferred locals initialized with conditional expressions retain their common branch type for member completion, navigation, and rename. Direct parenthesized expressions and chained delegate factory calls retain their result type too. `List.Repeat` results infer their element type from the value, explicit call-site type arguments, and expected list type, including for inferred locals and direct indexed access. Lambda returns use the delegate signature for target-typed constructors and enum argument suggestions. Member completion also follows optional access and null-forgiving receivers, including dictionary lookups.
948
-
949
- ## Member change listeners
950
-
951
- Subscribe to a writable stored instance member with `owner.Field.OnChanged +=
952
- receiver.Handler`; use `-=` to remove the same receiver/handler pair. Handlers
953
- are noncapturing, return void, and accept the field's type. Bindings to Session
954
- receivers stay Session-only. Gameplay hosts dispatch after the write batch.
955
- Authoring, replay, migration, and dialogue dry-run hosts retain wiring without
956
- invoking callbacks. `owner.Field.OnChanged.Clear()` removes every handler,
957
- including `@listeners` defaults, in both Save and Session wiring.
958
-
959
- Instance getters take the same syntax, for example
960
- `this._quantity.Value.OnChanged += this.QuantityChanged`. A getter
961
- subscription lives only in memory for the play session, so subscribe from a
962
- function, effect, lifecycle hook, or dialogue action, never a constructor,
963
- initializer, or migration. Subscribing reads the getter once. The SDK calls the
964
- handler with a fresh result whenever something that read depended on changes,
965
- even if the result is equal, and several changes before the next read call it
966
- once. `Clear()` ends the subscription, and a constructor can't call it on a
967
- getter. Static getters, `base.Value`, and `@listeners` can't observe a getter.
968
- `neo test` dispatches getter subscriptions the same way, treating the test body
969
- as the host: handlers run after each of its statements, so the next statement
970
- sees their effects. A getter that reads through `Clone()` or a grid query can
971
- run more often in `neo test` than in Unity: any write but its own handlers'
972
- counts as a change. Other TypeScript hosts validate getter subscriptions and
973
- never call the handler.
974
-
975
- Persist wiring on the owning class value with named arguments, for example:
976
-
977
- ```neo
978
- @listeners(Count: [root.Save.Tracker.CountChanged], Label: [root.Save.Tracker.LabelChanged])
979
- new Counter()
980
- ```
981
-
982
- Every supplied argument must name a writable stored field. Names are contextual
983
- and may appear in any order. Use the existing nested positional override for a
984
- virtual owner; changing its listeners alone must not add a value override.
985
- Omitting a field inherits its listeners. An empty array suppresses inherited
986
- listeners and disappears when there is no inherited wiring to suppress.
987
-
988
- Decimal collection membership compares numeric values: `1.0` and `1.00` are
989
- equal for `ToSet`, `Contains`, `IndexOf`, `Add`, and `Remove`. Decimal scale
990
- is retained for display; string entries such as `"1.0"` and `"1.00"` stay distinct.