@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.
- package/CHANGELOG.md +23 -0
- package/README.md +14 -0
- package/dist/neo-runtime.mjs +11153 -3060
- package/dist/test-worker.mjs +15214 -9707
- package/package.json +1 -1
- package/skills/neocompose-cli/SKILL.md +95 -279
- package/skills/neocompose-cli/references/cli-development.md +101 -117
- package/skills/neocompose-cli/references/declarations/annotations-and-storage.md +112 -0
- package/skills/neocompose-cli/references/declarations/constructors.md +164 -0
- package/skills/neocompose-cli/references/declarations/declarations.md +103 -0
- package/skills/neocompose-cli/references/declarations/extensions.md +46 -0
- package/skills/neocompose-cli/references/declarations/initializers.md +100 -0
- package/skills/neocompose-cli/references/declarations/member-kinds.md +107 -0
- package/skills/neocompose-cli/references/declarations/overloads.md +50 -0
- package/skills/neocompose-cli/references/dialogues.md +145 -0
- package/skills/neocompose-cli/references/neoscript/collections.md +138 -0
- package/skills/neocompose-cli/references/neoscript/control-flow.md +123 -0
- package/skills/neocompose-cli/references/neoscript/delegates-and-listeners.md +107 -0
- package/skills/neocompose-cli/references/neoscript/math-and-random.md +114 -0
- package/skills/neocompose-cli/references/neoscript/neoscript.md +113 -0
- package/skills/neocompose-cli/references/neoscript/strings.md +47 -0
- package/skills/neocompose-cli/references/neoscript/testing-and-migrations.md +73 -0
- package/skills/neocompose-cli/references/neoscript/types-and-narrowing.md +111 -0
- package/skills/neocompose-cli/references/sync/branches-and-history.md +69 -0
- package/skills/neocompose-cli/references/sync/conflicts-and-recovery.md +68 -0
- package/skills/neocompose-cli/references/sync/sync.md +96 -0
- package/skills/neocompose-cli/references/sync/unity-export.md +63 -0
- package/skills/neocompose-cli/references/values/defaults-and-localization.md +86 -0
- package/skills/neocompose-cli/references/values/files.md +71 -0
- package/skills/neocompose-cli/references/values/identities.md +97 -0
- package/skills/neocompose-cli/references/values/references.md +151 -0
- package/skills/neocompose-cli/references/values/values.md +110 -0
- package/skills/neocompose-cli/references/world/animation.md +133 -0
- package/skills/neocompose-cli/references/world/grids-and-cell-patterns.md +55 -0
- package/skills/neocompose-cli/references/world/variants.md +112 -0
- package/skills/neocompose-cli/references/world/world.md +105 -0
- package/skills/neocompose-cli/references/animation-and-world-authoring.md +0 -582
- package/skills/neocompose-cli/references/commands-and-sync.md +0 -408
- package/skills/neocompose-cli/references/declarations-and-construction.md +0 -773
- package/skills/neocompose-cli/references/neoscript.md +0 -990
- 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.
|