@neocompose/cli 0.87.1 → 0.88.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 (40) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +14 -0
  3. package/dist/neo-runtime.mjs +3789 -1141
  4. package/dist/test-worker.mjs +2450 -116
  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/initializers.md +100 -0
  12. package/skills/neocompose-cli/references/declarations/member-kinds.md +107 -0
  13. package/skills/neocompose-cli/references/declarations/overloads.md +50 -0
  14. package/skills/neocompose-cli/references/dialogues.md +145 -0
  15. package/skills/neocompose-cli/references/neoscript/collections.md +98 -0
  16. package/skills/neocompose-cli/references/neoscript/control-flow.md +123 -0
  17. package/skills/neocompose-cli/references/neoscript/delegates-and-listeners.md +107 -0
  18. package/skills/neocompose-cli/references/neoscript/math-and-random.md +114 -0
  19. package/skills/neocompose-cli/references/neoscript/neoscript.md +112 -0
  20. package/skills/neocompose-cli/references/neoscript/strings.md +47 -0
  21. package/skills/neocompose-cli/references/neoscript/testing-and-migrations.md +73 -0
  22. package/skills/neocompose-cli/references/neoscript/types-and-narrowing.md +111 -0
  23. package/skills/neocompose-cli/references/sync/branches-and-history.md +69 -0
  24. package/skills/neocompose-cli/references/sync/conflicts-and-recovery.md +68 -0
  25. package/skills/neocompose-cli/references/sync/sync.md +96 -0
  26. package/skills/neocompose-cli/references/sync/unity-export.md +47 -0
  27. package/skills/neocompose-cli/references/values/defaults-and-localization.md +86 -0
  28. package/skills/neocompose-cli/references/values/files.md +71 -0
  29. package/skills/neocompose-cli/references/values/identities.md +97 -0
  30. package/skills/neocompose-cli/references/values/references.md +151 -0
  31. package/skills/neocompose-cli/references/values/values.md +110 -0
  32. package/skills/neocompose-cli/references/world/animation.md +133 -0
  33. package/skills/neocompose-cli/references/world/grids-and-cell-patterns.md +55 -0
  34. package/skills/neocompose-cli/references/world/variants.md +112 -0
  35. package/skills/neocompose-cli/references/world/world.md +105 -0
  36. package/skills/neocompose-cli/references/animation-and-world-authoring.md +0 -582
  37. package/skills/neocompose-cli/references/commands-and-sync.md +0 -408
  38. package/skills/neocompose-cli/references/declarations-and-construction.md +0 -773
  39. package/skills/neocompose-cli/references/neoscript.md +0 -990
  40. package/skills/neocompose-cli/references/values-identities-and-references.md +0 -421
@@ -0,0 +1,100 @@
1
+ # Initializers
2
+
3
+ Member defaults: what they may read, when they compute, generic and
4
+ Immutable construction, and edits to stored constructions.
5
+
6
+ ## Initializer scope
7
+
8
+ - Field initializers may read their class's static members unqualified,
9
+ inherited ones included: `public int First = Run();`,
10
+ `public static int Copy = Number;`.
11
+ - Eager initializers can't read `this` or instance members. Defer such reads
12
+ in a lambda or method group. `List<int> Values = [this.Number + 1]` is an
13
+ error; `() => this.Number`, `Read`, and `new Callback { Read = Read }` are
14
+ fine.
15
+ - A static naming another static copies a value
16
+ (`public static int B = Holder.A;`). Class and collection statics already
17
+ own their value; use `Clone()` or `Reference`.
18
+ - Inherited initializers read parameters from the constructor that declared
19
+ them; a storage-only override keeps that scope.
20
+ - Omitted constructor fields use their defaults. Writable collection defaults
21
+ belong to each instance. Fields without defaults stay absent, and `null`
22
+ arguments to generated member-wise constructors mean omission.
23
+
24
+ ## Computed initializers
25
+
26
+ A default that isn't a closed literal stores as a computed initializer and
27
+ replays for omitted members in push, web, and SDK. A stored override stays
28
+ pinned until reset.
29
+
30
+ - Calls, operators (`public float Derived = Flags.Start / Flags.Scale;`), and
31
+ direct field or property reads (local, qualified static, or via a stable
32
+ root path) are computed. `protected int Slots = FeatureFlags.StartInventorySize;`
33
+ reads the current value at each construction instead of baking it in.
34
+ - Root paths such as `root.Save.World.Time.Year` stay executable even when
35
+ `World` and `Time` exist only through construction defaults, including
36
+ inside `new() { Year = root.Save.World.Time.Year }`.
37
+ - Literal-only positions: dictionary keys (string literals, or option literals
38
+ for enum keys) and Color components. Instance member defaults need numeric
39
+ literal vector components; statics may compute them
40
+ (`new Vector2(Probe.X, 1)`).
41
+ - Parenthesize a computed list or dictionary entry that has an `@id`:
42
+ `@id("row") (A.B / A.C)`. Push adds these parentheses when it assigns IDs.
43
+
44
+ ## Generic construction
45
+
46
+ - A generic class without a declared constructor is built with its type
47
+ arguments: `new Box<int> { Item = 4 }` or target-typed `new() { Item = 4 }`.
48
+ `new Box()` is an open generic and rejected. A `T` field is required unless
49
+ `T` is nullable (`new Box<int?>()` may omit it).
50
+ - Inside the class, construct with its own parameter
51
+ (`public Bag<T> B = new Bag<T>();`); one with fields or entries runs per
52
+ instance. Wrapping the parameter (`new Bag<List<T>>()`) is rejected.
53
+ - Type arguments must satisfy `extends` in bodies too. A written argument list
54
+ names every parameter (`item is Tool<Asset>` fails when `Tool` has two); a
55
+ bare `item is Tool` is allowed.
56
+ - Delegates may be type arguments (`Box<NeoDelegate<int, int>>`); `NeoAction`
57
+ may not.
58
+ - Header parameters may initialize generic collection fields
59
+ (`class Box<T>(List<T> value) { public List<T> Value = value; }`), also for
60
+ `Dictionary<string, T>`, nested collections, and stored instances such as
61
+ `public static Box<int> Value = new([1, 2]);`.
62
+ - Collection type arguments such as `new Box<List<int>>([1, 2])` work even if
63
+ no field declares that collection type. Every authored type argument is
64
+ validated, nested ones included: `new Box<List<MissingType>>()` fails.
65
+ - Generics may nest (`Box<Box<int>>`, `Box<List<Box<int>>>`); each `new` keeps
66
+ its own type arguments.
67
+
68
+ ## Immutable members
69
+
70
+ Runtime construction never supplies an Immutable member: no initializer
71
+ block, constructor body, base argument, or generated C# factory can set one,
72
+ and it is never stored with the instance.
73
+
74
+ - If the declaration and every row it owns read neither `this` nor
75
+ constructor arguments, and no value of its type holds a writable member, it
76
+ is a constant shared by every instance.
77
+ - Otherwise each instance evaluates the initializer with its own `this` and
78
+ arguments, again after reload. A method group such as
79
+ `selector: SelectTorso` binds `this`.
80
+ - `Clone()` shares an authored or constant Immutable value and re-evaluates a
81
+ per-instance one.
82
+ - A class with a required Immutable member and no initializer can only be
83
+ constructed by authored values.
84
+
85
+ ## Stored constructions
86
+
87
+ - Editing a stored object's constructor arguments rebuilds it in place under
88
+ the same identity; see [defaults and overrides](../values/defaults-and-localization.md#construction-rebuilds).
89
+ Initializers that read it wait for the replacement.
90
+ - Objects built by an implicit constructor may settle required fields in
91
+ their object body. Keep that body when adding a parameterless constructor;
92
+ an empty argument list needs no invented arguments.
93
+ - Populated unordered defaults (`@settings(kind: .Unordered)` with
94
+ `List<Entry> Entries = [new Entry()]`) are built per instance by an
95
+ initializer. Nested unordered lists keep their stored values.
96
+ - A field, entry, or dictionary value can't place something already owned
97
+ elsewhere (`Bag = [Shared.Pattern]`, or a constructor parameter stored in an
98
+ ordinary field). Use `Clone()` or `Reference`.
99
+ - You may remove a constructor and replace every call in the same edit; push
100
+ prepares the new calls with the schema change.
@@ -0,0 +1,107 @@
1
+ # Member kinds
2
+
3
+ Interface-typed members, sets, actions, lifecycle hooks, and stored tuples.
4
+ `Reference<T>` lookups are in [references](../values/references.md).
5
+
6
+ ## Interface-typed members
7
+
8
+ An interface may type a field (nullable, `readonly`, or `static` too), a
9
+ `List`/`Set`/`Dictionary` entry, a generic argument, a parameter, or
10
+ `Reference<I>`. Each row holds its own class, which must implement it.
11
+
12
+ ```neo
13
+ interface IContainer<T extends Item> {
14
+ List<T> Contents { get; }
15
+ }
16
+
17
+ class Crate<T extends Item> : IContainer<T> {
18
+ public List<T> Contents = [];
19
+ }
20
+
21
+ class Player {
22
+ public IContainer<Seed>? Pouch = new Crate<Seed>();
23
+ public List<IContainer<Seed>> Racks = [];
24
+ }
25
+ ```
26
+
27
+ - Construct a class, never the interface (`new IContainer<Seed>()` is an
28
+ error).
29
+ - An open class fits an interface slot only when the interface fills every
30
+ class parameter, as `Crate<T> : IContainer<T>` does; otherwise store a
31
+ closed subclass.
32
+ - A class implements a given interface once across its `extends` chain.
33
+ Interface arguments are in-scope type parameters or types containing none
34
+ (`IContainer<List<T>>` is an error). `is` checks need the arguments.
35
+ - `NeoVariant<I>` is an error. `@index` on interface entries is an error;
36
+ `@column` may name an interface property.
37
+ - Interfaces carry no storage. A row fits only when its class allows the
38
+ storage it lives in; the compiler, push, and commit refuse misfits.
39
+ - A variant or animation override that puts a different class in an
40
+ interface slot replaces the slot's row.
41
+ - Constraints may be interfaces (`T extends IContainer<Seed>`).
42
+ - Generated C# types the slot by the generated interface. Assigning a non-Neo
43
+ C# implementation, or a row the interface doesn't determine, throws and
44
+ names the slot.
45
+
46
+ ## Sets
47
+
48
+ `Set<T>` is insertion-ordered and holds no duplicates. The element decides
49
+ storage:
50
+
51
+ - enum `Set<Trait>`: multi-select enum
52
+ - `Set<Reference<T>>`: multi-select lookup
53
+ - `Set<NeoDialogueReference>`: multi-select dialogue lookup
54
+ - anything else: an owned set of rows, like a List
55
+
56
+ ```neo
57
+ @index(member: Slug, kind: .Unique)
58
+ public Set<Quest> ActiveQuests = [];
59
+ public Set<int> LuckyNumbers = [3, 7];
60
+ public Set<Trait> Traits = [.Brave];
61
+ ```
62
+
63
+ - Owned sets hold `bool`, `int`, `float`, `decimal`,
64
+ `@settings(format: .Plain) string`, vectors, `Color`, sprites, audio clips,
65
+ classes, or interfaces. Not nullables, collections, variants, delegates, or
66
+ actions. Class entries are unique by row, values by value.
67
+ - `@index`/`@column` work as on a List; `kind:` is an error.
68
+ - `selection:` is an error everywhere: `Set<T>` selects many, `T` one. The
69
+ language server's quick fix rewrites `@settings(selection: .Multi)` to a
70
+ `Set<...>` type.
71
+ - A default literal that repeats a value fails push. `List<Trait>` stays an
72
+ ordered list.
73
+ - Constructor mutations such as `Bag.Add(7)` keep entries as backing rows;
74
+ existing entries keep their identities.
75
+ - Use `Set<T>` parameters for several selections.
76
+
77
+ ## Actions
78
+
79
+ Declare multicast events as `NeoAction<...>` with a bracketed list of method
80
+ groups: `public NeoAction<int> OnDamaged = [PlayHitFlash, ApplyDamage];`.
81
+ Invocation, subscription, and `Clear()` rules are in
82
+ [delegates and listeners](../neoscript/delegates-and-listeners.md#actions).
83
+
84
+ ## Lifecycle interfaces
85
+
86
+ - `INeoOnLoad`/`INeoOnUnload` run when an instance becomes live and stops
87
+ being live.
88
+ - `INeoUnity*` interfaces (`INeoUnityUpdate`, `INeoUnityOnTriggerEnter2D`, ...)
89
+ run for a rendered `NeoObjectBase` in play mode.
90
+ - Their member names (`OnLoad`, `Update`, ...) are reserved: declare one only
91
+ as the public immediate instance method implementing its interface, never
92
+ `@effect`.
93
+ - Subscribe Session actions in `OnLoad` and remove them in `OnUnload`. Use
94
+ `@effect` for data reactions and keep `Update` for true per-frame work.
95
+
96
+ ## Stored tuples
97
+
98
+ ```neo
99
+ public (int Count, bool Enabled) State = (3, true);
100
+ public (int, (string Label, int Count))? Detail = null;
101
+ ```
102
+
103
+ Each position has a derived descriptor; author no descriptor IDs. Relabeling
104
+ keeps identity. Changing arity or moving positions needs a migration. A
105
+ required tuple without a default must be supplied at construction. Owned
106
+ class and collection elements follow the single-owner rule. Tuple
107
+ expressions are in [types and narrowing](../neoscript/types-and-narrowing.md#tuples).
@@ -0,0 +1,50 @@
1
+ # Overloads
2
+
3
+ ## Declaring
4
+
5
+ ```neo
6
+ class Inventory {
7
+ public int Add(Item item) { return 1; }
8
+ public int Add(Item item, int count) { return count; }
9
+ public int Add(List<Item> items) { return items.Count; }
10
+ }
11
+ ```
12
+
13
+ - A class's overload set holds every same-named function it declares or
14
+ inherits. Static and instance, NSFunction and native, generic and
15
+ non-generic functions mix freely.
16
+ - Any two need different parameter types after dropping nullability,
17
+ substituting base type arguments, and renaming function type parameters by
18
+ position. `Add(Item)` and `Add(Item?)` collide, so do `F()` and `F<T>()`;
19
+ `Find<T = Item>(T value)` occupies `Find(Item)`.
20
+ - At most one function per name and class may be dispatched (`abstract`,
21
+ `virtual`, `override`, or an interface implementation). Overrides target
22
+ that one.
23
+ - Interfaces can't overload. When several class functions share an interface
24
+ function's name, the one with matching parameters implements it.
25
+ - `Equals` and names a platform type declares (lifecycle hooks,
26
+ `OnTriggerEnter2D`) can't be overloaded. A function can't share a name with
27
+ a field, property, getter, or action.
28
+ - Overloads generate C# overloads. A plain overload stores under the key
29
+ `Name#<memberId>`; a dispatched or lone function keeps `Name`.
30
+
31
+ ## Calling
32
+
33
+ Resolution happens at compile time over every overload the receiver's type
34
+ sees. A type qualifier sees static ones, an instance receiver instance ones.
35
+
36
+ - An overload applies when the argument count fits its required plus default
37
+ parameters and each argument converts. Lambdas, method groups, `null`,
38
+ collection literals, and `.North` apply by shape.
39
+ - Closer parameter types win: exact beats conversion, `Bag` beats `Item` when
40
+ `Bag : Item`. Ties go to the overload filling no defaults, then the
41
+ non-generic one. Anything else is ambiguous: `Scale(float)` and
42
+ `Scale(decimal)` can't take an `int`; write `2.0` or `2m`.
43
+ - Generic overloads take explicit type arguments or infer them. One whose
44
+ arguments can't be inferred isn't a candidate: beside `F(int a, string b)`,
45
+ `F<T>(T a, T b)` takes `F(1, 2)` but not `F(1, "a")`.
46
+ - Method groups pick the overload exactly matching the target delegate type
47
+ in assignments, `+=`, `OnChanged`, `@listeners`, and delegate arguments.
48
+ `var f = Read` is an error for an overloaded `Read`.
49
+ - Tests and eval name a delegate type or signature; see
50
+ [testing](../neoscript/testing-and-migrations.md).
@@ -0,0 +1,145 @@
1
+ # Dialogues
2
+
3
+ NeoFlow `.neoflow` files, dialogue groups, labels, and triggering dialogues
4
+ from NeoScript.
5
+
6
+ ## Dialogue files
7
+
8
+ Each `.neoflow` file has exactly one top-level, non-generic
9
+ `sealed class Name : NeoDialogue` that overrides `Trigger Trigger` once and
10
+ declares no other types.
11
+
12
+ ```neoflow
13
+ @id("capitol-dialogue-id")
14
+ @settings(name: "Capitol: cold boot", saveOptionChoices: true)
15
+ sealed class CapitolColdBoot : NeoDialogue {
16
+ @primary Outpost capitol = Assets.Capitol;
17
+ Player player = Player.Current;
18
+
19
+ @id("trigger-id")
20
+ override Trigger Trigger = new(group: CapitolDialogues.High) => Welcome;
21
+
22
+ @id("welcome-node-id")
23
+ Text Welcome = new(name: "Welcome!") {
24
+ """
25
+ Hello, {player.Name}.
26
+ """
27
+
28
+ @id("continue-option-id")
29
+ Option Continue = new() {
30
+ """
31
+ Continue.
32
+ """
33
+ return Finish;
34
+ }
35
+ }
36
+
37
+ @id("finish-node-id")
38
+ Text Finish = new() {
39
+ """
40
+ Until next time.
41
+ """
42
+ }
43
+ }
44
+ ```
45
+
46
+ - Mark `@primary` explicitly; never infer it. In a `.Lookup` group the
47
+ `@primary` binding is the lookup value that triggers the dialogue; without
48
+ one it never triggers.
49
+ - Dialogue bindings are flow-wide; node bindings are scoped to the node and
50
+ its children.
51
+ - Each persisted condition use, action invocation, mutation, pause, option,
52
+ and node has its own owner-scoped ID. New ones omit it until push.
53
+ - Go to a destination with `= new(...) => Next;` or a trailing block with
54
+ `return Next;`. Never author `to:`. Triggers, options, and outcomes need
55
+ destinations; terminal text and actions may fall through. Keep every
56
+ statement of `Actions Empty = new();` inside its body.
57
+ - Prose lowers to localized-text records. Existing translations survive
58
+ pushes. A prose block's content and closing `"""` follow its opening
59
+ `"""`, so re-indenting never changes the text.
60
+ - New getters used in prose interpolation need no `@id`.
61
+
62
+ After editing, run `neo dialogue dryrun <dialogue-ref>`. It prepares the
63
+ local candidate, including pending schema and value changes, without fetching
64
+ or committing. Exit 1 means the graph would fail on device.
65
+
66
+ ## Groups
67
+
68
+ ```neo
69
+ NeoDialogueGroup QuestUnlocks = new(kind: .Standard, parent: Quests, name: "Unlocks");
70
+
71
+ NeoDialogueGroup FishingVictory = new(
72
+ kind: .Lookup,
73
+ collection: Assets.FishEncounters,
74
+ collectionValue: Reference(root.Assets.FishEncounters),
75
+ );
76
+ ```
77
+
78
+ - `name:` sets the stored name when it should differ from the local
79
+ identifier; omitted, it uses the identifier. Unity generates its API from
80
+ the stored name (`Quests.Unlocks`). Pull keeps the local identifier and file
81
+ by `@id`; a remote rename updates `name:`.
82
+ - Triggers accept `group: QuestUnlocks.Medium` or
83
+ `group: Quests.Unlocks.Medium`. Qualified paths use display names converted
84
+ to identifiers. Sibling groups that collide on a segment need distinct local
85
+ aliases.
86
+ - Priority groups are `NeoPriorityGroup` declarations.
87
+ - Lookup groups use the same collection paths as lookup members. The
88
+ collection may be created in the same push without IDs. Values without a
89
+ root path keep `Reference(id: "...")`.
90
+
91
+ ## Labels
92
+
93
+ For `Text`, `Actions`, `Conditions`, `Option`, and `Outcome`, an omitted
94
+ `name:` takes the label from a custom symbol: `Option Accept = new()` is
95
+ labeled `Accept` whatever its prose. Dialogue classes do the same without
96
+ `@settings(name:)`.
97
+
98
+ - Generated symbols (`Text1`, `Actions2`, `Option1_2`, `Outcome1`, `Node_...`)
99
+ stay unlabeled for their own kind. A symbol from another kind is custom:
100
+ `Option Text1` is labeled `Text1`.
101
+ - Explicit names win: `name: ""` clears a label, `name: null` clears an
102
+ optional graph label. Pull writes explicit empty names where omission would
103
+ infer one and drops names equal to the inferred symbol.
104
+ - `Trigger` never infers a label. Reusable condition and action functions use
105
+ their function name as `sourceFunctionName`; rename the function and its
106
+ uses to change it. Linked-value aliases keep their `sourceName`. Labels
107
+ never affect IDs or destinations.
108
+ - `neo pull --regenerate-source-names` replaces generated dialogue symbols
109
+ with readable ones and rewrites references.
110
+
111
+ ## Triggering dialogues from NeoScript
112
+
113
+ `root.Dialogues` mirrors the generated C# `Dialogues` folders and groups.
114
+
115
+ ```neo
116
+ public bool TryTalk() {
117
+ return root.Dialogues.NPCs.Talk.TryTrigger(this, () => {
118
+ this.AfterTalk();
119
+ });
120
+ }
121
+
122
+ public bool HasSomethingToSay => root.Dialogues.NPCs.Talk.CanTrigger(this);
123
+ ```
124
+
125
+ - `root.Dialogues` has `TryTrigger(string dialogueId, onFinish)`,
126
+ `CanTrigger(string dialogueId)`, `VisitCount(pointer)`, and
127
+ `HasVisited(pointer)`.
128
+ - A standard group has `TryTrigger(onFinish)` and `CanTrigger()`. A lookup
129
+ group takes a stored row of its entry class: `TryTrigger(value, onFinish)`,
130
+ `CanTrigger(value)`.
131
+ - `NeoDialogueReference` has `id`, `TryTrigger(onFinish)`, and `CanTrigger()`.
132
+ `Set<NeoDialogueReference>` elements are bare IDs; pass them to
133
+ `root.Dialogues.TryTrigger`.
134
+ - `onFinish` is an optional `NeoDelegate<void>` that runs when the dialogue
135
+ finishes, fails, or is disposed.
136
+ - `TryTrigger` compiles only in functions (effects, lifecycle hooks,
137
+ listeners included) and dialogue actions. `CanTrigger`, `VisitCount`, and
138
+ `HasVisited` are reads. Folders and groups aren't values. User-class code
139
+ can't trigger dialogues.
140
+ - Unity queues accepted requests and shows one at a time through
141
+ `Dialogues.OnTrigger`. Requests for the running or a queued dialogue return
142
+ false, as does everything when the game has no `OnTrigger` handler.
143
+ - `neo test`, `neo script eval`, dry runs, and the web editor have no dialogue
144
+ runtime: `TryTrigger` and `CanTrigger` return false and `onFinish` never
145
+ runs.
@@ -0,0 +1,98 @@
1
+ # Collection builtins
2
+
3
+ Queries and mutations on `List`, `Set`, and `Dictionary` values. Collection
4
+ declarations and storage live in
5
+ [member kinds](../declarations/member-kinds.md).
6
+
7
+ ## Counting and matching
8
+
9
+ - `items.Count` and `items.Count()` give the size; `items.Count(pred)` counts
10
+ matches without building a list. Same forms on Set and Dictionary.
11
+ - `Any()`, `Any(pred)`, and `All(pred)` short-circuit. Empty input has
12
+ `Any() == false` and `All(...) == true`.
13
+ - `First`, `FirstOrDefault`, `Last`, `LastOrDefault`, `Single`, and
14
+ `SingleOrDefault` take zero arguments or one predicate. `First`/`Last` throw
15
+ on no match. `Single` throws unless exactly one matches; `SingleOrDefault`
16
+ still throws on several. `OrDefault` forms return nullable entries and
17
+ `null` on no match. Enum sets return enum values.
18
+ - Dictionary callbacks take `(key, value)`, as in
19
+ `Where((key, value) => key == ToolType.Pickaxe)`.
20
+
21
+ ## Queries
22
+
23
+ `Where`, `Select`, `SelectMany`, `OrderBy`, `OrderByDescending`, `Skip`,
24
+ `Take`, `SkipWhile`, `TakeWhile`, `Reverse()`, and `Concat(other)` return
25
+ eager, read-only lists. Queries over a Set, such as `Where` and `OrderBy`,
26
+ return a Set; `ToSet()` turns a List into one.
27
+
28
+ ```neo
29
+ return this.Beds
30
+ .SelectMany((bed) => bed.Plants)
31
+ .Where((plant) => plant.Ready)
32
+ .OrderBy((plant) => plant.Name)
33
+ .Take(10);
34
+ ```
35
+
36
+ - `SelectMany` needs a non-null list or set from its selector.
37
+ - Sorting is stable, evaluates each key once, compares strings ordinally, and
38
+ accepts bool, int, float, decimal, or string keys (nullable too). Null sorts
39
+ first ascending and last descending.
40
+ - `ThenBy`/`ThenByDescending` follow `OrderBy`. An inferred `var` keeps that
41
+ ability; List/Set annotations, function boundaries, and non-ordering queries
42
+ erase it. Ordering costs O(n log n * k) for k key levels. Mutating the
43
+ snapshot through a writable alias makes `ThenBy` throw until a new `OrderBy`.
44
+ - Negative skip/take counts behave as zero.
45
+ - Read-only results keep entry identity and grant no extra write access to
46
+ entries.
47
+
48
+ ## Reductions
49
+
50
+ - `Sum`, `Average`, `Min`, `Max` take zero arguments or one selector.
51
+ Sum/Average need int, float, or decimal; Min/Max also accept strings and
52
+ bools. Nulls are skipped. `Sum` of empty input is zero; the others return
53
+ null for nullable input and throw for required input. Average of ints is
54
+ float. Decimal math stays exact; decimal Average rounds half-even within 28
55
+ digits. Float `Min` stops at the first NaN; `Max` prefers ordinary values.
56
+ - `MinBy(sel)`/`MaxBy(sel)` return the first entry with the winning key,
57
+ skipping null keys unless all are null. Empty input throws unless the entry
58
+ type is nullable.
59
+ - List and Set have `Aggregate(fn)`, `Aggregate(seed, fn)`, `Distinct()`, and
60
+ `Zip(other, fn)`. An empty unseeded `Aggregate` throws. `Distinct` keeps
61
+ first occurrences by `Contains` equality. `Zip` stops at the shorter input.
62
+ - `GroupBy(sel)` returns a read-only dictionary of read-only lists.
63
+ `ToDictionary(keySel[, valueSel])` keys must be non-null strings or enums;
64
+ duplicate keys throw.
65
+ - `.Keys` and `.Values` return read-only lists in dictionary order;
66
+ integer-looking string keys come before other strings, in numeric order.
67
+
68
+ ## Mutation
69
+
70
+ These apply to writable collections and are statements:
71
+
72
+ - `items.Insert(index, value)` accepts 0 through `items.Count` (append);
73
+ other indices throw.
74
+ - `items.Remove(value)` removes the first equal entry; a missing value is a
75
+ no-op. `items.IndexOf(value)` (lists only) returns the first index or `-1`.
76
+ - `items.Sort()` sorts scalar entries; `items.Sort((l, r) => l.Score - r.Score)`
77
+ takes an int comparison. Sort is stable, keeps identity, rejects mutating
78
+ the list through aliases while comparing, and leaves the order unchanged if
79
+ a comparison fails.
80
+
81
+ `Contains`, `IndexOf`, `Remove`, `Distinct`, and `ToSet` compare classes,
82
+ interfaces, collections, and references by identity and other values by value.
83
+ Decimals compare numerically (`1.0` equals `1.00`) while keeping their scale
84
+ for display; strings such as `"1.0"` and `"1.00"` stay distinct.
85
+
86
+ ## List.Repeat
87
+
88
+ ```neo
89
+ List<int> scores = List.Repeat(0, 10);
90
+ List<InventoryItemStack?> slots = List.Repeat<InventoryItemStack?>(null, slotCount);
91
+ ```
92
+
93
+ The entry type comes from the value joined with the assignment context, like
94
+ a list literal, so `List.Repeat(0, 4)` assigned to `List<float>` holds floats.
95
+ Put type arguments on the call, never the qualifier: `List<int>.Repeat` is an
96
+ error and bare `List` is not a value. `count` must be an `int`; a negative
97
+ count throws at runtime. The value and then the count evaluate exactly once,
98
+ so a reference-typed value repeats the same reference.
@@ -0,0 +1,123 @@
1
+ # Control flow
2
+
3
+ Conditionals, loops, switch, and errors. The shapes match C#; this page lists
4
+ where NeoScript differs or restricts.
5
+
6
+ ## Conditional and logical operators
7
+
8
+ `cond ? a : b` binds looser than `??`, `||`, and `&&`, associates right, and
9
+ evaluates only the chosen arm. Both arms need a common assignable type or must
10
+ fit the expected type. `&&` binds tighter than `||`; both short-circuit.
11
+
12
+ ## Loops
13
+
14
+ `for`, `foreach`, `while`, and `do { } while (...);` work as in C#. `while`
15
+ and `do` bodies need braces. `break` exits the nearest loop or switch; `continue` targets the
16
+ nearest loop, including from a switch inside a loop, and re-checks `while`
17
+ conditions.
18
+
19
+ A `foreach` receiver must be non-null. The loop snapshots membership and order
20
+ at entry, so adding or removing rows does not change its iterations.
21
+ `foreach (var value in dictionary)` binds values. Deconstruct to get keys:
22
+
23
+ ```neo
24
+ foreach (var (key, value) in this.Damage) {
25
+ if (key == ToolType.Pickaxe) {
26
+ ApplyDamage(value);
27
+ }
28
+ }
29
+ foreach ((ToolType key, float value) in this.Damage) { ApplyDamage(value); }
30
+ ```
31
+
32
+ Deconstruction needs a dictionary local or member path; assign a computed
33
+ receiver to a local first. Keys are snapshotted at entry and each value is
34
+ read through `dictionary[key]`, so a removed key follows normal missing-key
35
+ behavior. Both bindings are read-only; writes through a writable entry still
36
+ work. Enum-keyed dictionaries yield enum keys.
37
+
38
+ ## Switch statements
39
+
40
+ Selectors are `int`, `string`, `bool`, enum, or nullable forms of these, with
41
+ compile-time constant labels. Stack labels to share a body. End every
42
+ reachable non-empty section with `break`, `return`, `throw`, or a valid
43
+ `continue`. No implicit fallthrough, pattern cases, guards, or `goto`.
44
+
45
+ ```neo
46
+ switch (direction) {
47
+ case Direction.North:
48
+ Move(0, 1);
49
+ break;
50
+ case Direction.East:
51
+ case Direction.West:
52
+ Stop();
53
+ break;
54
+ default:
55
+ throw $"Unknown direction: {direction}";
56
+ }
57
+ ```
58
+
59
+ ## Switch expressions
60
+
61
+ `value switch { pattern => result, ... }` arms take constants, `null`, type
62
+ patterns (`Damage`), declaration patterns (`Damage damage`), or `_`, each
63
+ with an optional `when` guard. Arms test in order; the selector runs once.
64
+
65
+ ```neo
66
+ closed class HitResult {}
67
+ sealed class Miss : HitResult {}
68
+ sealed class Damage : HitResult { public int Amount = 0; }
69
+
70
+ class Combat {
71
+ public string Describe(HitResult result) => result switch {
72
+ Miss => "miss",
73
+ Damage damage when damage.Amount > 10 => "strong hit",
74
+ Damage damage => $"{damage.Amount}",
75
+ };
76
+ }
77
+ ```
78
+
79
+ Switch expressions must be exhaustive: every bool/enum option or
80
+ closed-class branch, plus `null` for nullable input; other types need a
81
+ covering type or `_`. A guard counts toward coverage only if it is literally
82
+ `true`. Missing and unreachable arms are compile errors. Property,
83
+ relational, list, and positional patterns are unsupported.
84
+
85
+ ## Errors
86
+
87
+ Throw a string or a `NeoError` subclass; a string throw creates a `NeoError`.
88
+ Every error has a read-only `Message`. Subclass `NeoError` for typed data.
89
+
90
+ ```neo
91
+ try {
92
+ return GetUnsafe();
93
+ }
94
+ catch (NeoArgumentError error) when (error.Message != "") {
95
+ Report(error.Message);
96
+ throw;
97
+ }
98
+ catch (NeoError error) {
99
+ return Fallback();
100
+ }
101
+ finally {
102
+ ReleaseResource();
103
+ }
104
+ ```
105
+
106
+ - A try needs a catch, a finally, or both. Catch bindings are read-only.
107
+ - Typed catches match subclasses. `catch (string message)` matches every
108
+ ordinary error and binds its Message. An unfiltered base catch must follow
109
+ the catches it covers.
110
+ - Filters run before intervening finally blocks, even across calls. A false or
111
+ failing filter continues the search. Bare `throw;` rethrows from a catch.
112
+ - Finally runs on normal completion, return, break, continue, and ordinary
113
+ errors. The return value is computed before cleanup. An error escaping
114
+ finally replaces the pending one; an error caught inside it does not.
115
+ Return and loop transfers can't leave a finally block (a nested loop may
116
+ break or continue), and bare `throw` inside it needs its own inner catch.
117
+ - Runtime failures are `NeoNullReferenceError`, `NeoIndexOutOfRangeError`,
118
+ `NeoKeyNotFoundError`, `NeoArgumentError`, `NeoArithmeticError`, and
119
+ `NeoInvalidOperationError`. Unexpected native failures become `NeoHostError`
120
+ with Message `"Check logs for more details."`.
121
+ - Execution limits, unavailable preview natives, cancellation, and invalid
122
+ compiled data can't be caught. Completed writes are never rolled back.
123
+ Suspended Unity calls keep their cleanup until resumed; abandonment skips it.