@neocompose/cli 0.87.2 → 0.89.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +14 -0
  3. package/dist/neo-runtime.mjs +11153 -3060
  4. package/dist/test-worker.mjs +15214 -9707
  5. package/package.json +1 -1
  6. package/skills/neocompose-cli/SKILL.md +95 -279
  7. package/skills/neocompose-cli/references/cli-development.md +101 -117
  8. package/skills/neocompose-cli/references/declarations/annotations-and-storage.md +112 -0
  9. package/skills/neocompose-cli/references/declarations/constructors.md +164 -0
  10. package/skills/neocompose-cli/references/declarations/declarations.md +103 -0
  11. package/skills/neocompose-cli/references/declarations/extensions.md +46 -0
  12. package/skills/neocompose-cli/references/declarations/initializers.md +100 -0
  13. package/skills/neocompose-cli/references/declarations/member-kinds.md +107 -0
  14. package/skills/neocompose-cli/references/declarations/overloads.md +50 -0
  15. package/skills/neocompose-cli/references/dialogues.md +145 -0
  16. package/skills/neocompose-cli/references/neoscript/collections.md +138 -0
  17. package/skills/neocompose-cli/references/neoscript/control-flow.md +123 -0
  18. package/skills/neocompose-cli/references/neoscript/delegates-and-listeners.md +107 -0
  19. package/skills/neocompose-cli/references/neoscript/math-and-random.md +114 -0
  20. package/skills/neocompose-cli/references/neoscript/neoscript.md +113 -0
  21. package/skills/neocompose-cli/references/neoscript/strings.md +47 -0
  22. package/skills/neocompose-cli/references/neoscript/testing-and-migrations.md +73 -0
  23. package/skills/neocompose-cli/references/neoscript/types-and-narrowing.md +111 -0
  24. package/skills/neocompose-cli/references/sync/branches-and-history.md +69 -0
  25. package/skills/neocompose-cli/references/sync/conflicts-and-recovery.md +68 -0
  26. package/skills/neocompose-cli/references/sync/sync.md +96 -0
  27. package/skills/neocompose-cli/references/sync/unity-export.md +63 -0
  28. package/skills/neocompose-cli/references/values/defaults-and-localization.md +86 -0
  29. package/skills/neocompose-cli/references/values/files.md +71 -0
  30. package/skills/neocompose-cli/references/values/identities.md +97 -0
  31. package/skills/neocompose-cli/references/values/references.md +151 -0
  32. package/skills/neocompose-cli/references/values/values.md +110 -0
  33. package/skills/neocompose-cli/references/world/animation.md +133 -0
  34. package/skills/neocompose-cli/references/world/grids-and-cell-patterns.md +55 -0
  35. package/skills/neocompose-cli/references/world/variants.md +112 -0
  36. package/skills/neocompose-cli/references/world/world.md +105 -0
  37. package/skills/neocompose-cli/references/animation-and-world-authoring.md +0 -582
  38. package/skills/neocompose-cli/references/commands-and-sync.md +0 -408
  39. package/skills/neocompose-cli/references/declarations-and-construction.md +0 -773
  40. package/skills/neocompose-cli/references/neoscript.md +0 -990
  41. package/skills/neocompose-cli/references/values-identities-and-references.md +0 -421
@@ -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.
@@ -0,0 +1,107 @@
1
+ # Delegates, actions, and listeners
2
+
3
+ Lambdas, `NeoDelegate`, method groups, `NeoAction` events, `OnChanged`
4
+ subscriptions, and `@listeners` wiring.
5
+
6
+ ## Lambdas and delegates
7
+
8
+ ```neo
9
+ NeoDelegate<int, int> twice = (x) => x * 2;
10
+ List<Item> ready = this.Items.Where((item) => item.Count > 0);
11
+ ```
12
+
13
+ - Lambdas may be returned, stored, or assigned to typed locals. They capture
14
+ surrounding locals and parameters by value when created.
15
+ - `(x) => x * 2` equals `(x) => { return x * 2; }`. In a void delegate the
16
+ expression must call or assign. Parenthesize a dictionary-literal body:
17
+ `() => ({ "a": 1 })`.
18
+ - Call any non-null delegate expression directly: `callbacks[0](value)`,
19
+ `(cond ? a : b)()`, `Factory.Make()()`. Force-unwrap a nullable one first:
20
+ `callbacks["key"]!()`. Void delegates are statements only.
21
+ - Inside a class body, invoke delegate and action members directly:
22
+ `Selector()`, `OnUpdated()`.
23
+
24
+ ## Delegate field defaults
25
+
26
+ - Instance delegate defaults compile against the declared signature; each
27
+ instance binds `this` to itself. Invalid bodies are reported at the
28
+ declaration.
29
+ - Static delegate fields use static class scope: no `this`, no instance
30
+ members. A static delegate cannot copy another class's delegate field,
31
+ inherited ones included; wrap it as `() => Other.Call()`.
32
+ - A default may name a method group bare. `Value = Read` or `this.Read` binds
33
+ each instance to its own `Read`. A qualified group (`Holder.Read`) must be
34
+ static. Signature mismatches are reported at the declaration.
35
+ - When a function is overloaded, a method group picks the overload whose
36
+ parameters exactly match the target delegate type. `var f = Read` is an
37
+ error then; give `f` a delegate type. See [overloads](../declarations/overloads.md).
38
+
39
+ ## Actions
40
+
41
+ `NeoAction<...>` is a multicast event whose value is an ordered listener set.
42
+ Bare `NeoAction` takes no arguments; `NeoAction<void>` is invalid.
43
+
44
+ ```neo
45
+ public NeoAction<int> OnDamaged = [PlayHitFlash, ApplyDamage];
46
+ ```
47
+
48
+ - Default it with a bracketed list of method groups. Never a lambda, never a
49
+ bare method group, never `required`; its rest state is the empty set.
50
+ - Subscribe and unsubscribe with `+=` and `-=` on the member. These are
51
+ durable writes deduplicated by target, so re-running a subscription can't
52
+ double-subscribe. External public callers may subscribe and unsubscribe
53
+ only.
54
+ - Only the declaring class and subclasses may invoke it (as a statement) or
55
+ call `this.OnUpdated.Clear()`. `Clear()` follows the member's storage and
56
+ writability. An action is never a value, a return type, or assigned with `=`.
57
+ - A nested default such as `Watcher = new() { OnTally = [Handle] }` may
58
+ target an inherited generic method on the enclosing instance and binds to
59
+ that instance. Explicit receivers keep their captured instance.
60
+ - An instance-bound listener reachable from a stable root keeps its path
61
+ (`[root.Assets.OnDamage]`). Pull refuses a bound receiver with no
62
+ deterministic `root.*` path.
63
+
64
+ ## OnChanged on fields
65
+
66
+ ```neo
67
+ owner.Field.OnChanged += receiver.Handler;
68
+ owner.Field.OnChanged -= receiver.Handler;
69
+ owner.Field.OnChanged.Clear();
70
+ ```
71
+
72
+ The field must be a writable stored instance member. Handlers are
73
+ noncapturing, return void, and accept the field's type. Bindings to Session
74
+ receivers stay Session-only. Gameplay hosts dispatch after the write batch;
75
+ authoring, replay, migration, and dialogue dry-run hosts keep the wiring but
76
+ never call handlers. `Clear()` removes every handler, `@listeners` defaults
77
+ included, in both Save and Session wiring.
78
+
79
+ ## OnChanged on getters
80
+
81
+ `this._quantity.Value.OnChanged += this.QuantityChanged` subscribes to an
82
+ instance getter. The subscription lives in memory for the play session, so
83
+ subscribe from a function, effect, lifecycle hook, or dialogue action, never
84
+ a constructor, initializer, or migration.
85
+
86
+ - Subscribing reads the getter once. The handler then gets a fresh result
87
+ whenever a dependency changes, even if the result is equal; several changes
88
+ before the next call produce one call.
89
+ - `Clear()` ends it; constructors can't call it on a getter. Static getters,
90
+ `base.Value`, and `@listeners` can't observe getters.
91
+ - `neo test` dispatches after each test-body statement, so the next statement
92
+ sees handler effects. Getters reading through `Clone()` or grid queries may
93
+ fire more often there than in Unity. Other TypeScript hosts validate but
94
+ never call handlers.
95
+
96
+ ## Persisted wiring
97
+
98
+ ```neo
99
+ @listeners(Count: [root.Save.Tracker.CountChanged], Label: [root.Save.Tracker.LabelChanged])
100
+ new Counter()
101
+ ```
102
+
103
+ Each named argument must be a writable stored field; order is free. For a
104
+ virtual owner, use the existing nested positional override; changing only
105
+ its listeners must not add a value override. An omitted field inherits its
106
+ listeners. `[]` suppresses inherited listeners and disappears when nothing is
107
+ inherited.
@@ -0,0 +1,114 @@
1
+ # Math and random
2
+
3
+ Numeric types, operators, `Math`, `NeoRandom`, and vector/Color math.
4
+
5
+ ## Numeric types
6
+
7
+ `int` retains whole-number precision; division truncates toward zero.
8
+ `float` literals, stored values, and
9
+ every float arithmetic step round to 32 bits, matching Unity. `decimal` is
10
+ exact base-10; use it for currency and other values that must not drift.
11
+ Float literals use `0.5`, not `0.5f`; decimal literals use `0.5m`.
12
+
13
+ ## Bitwise operators
14
+
15
+ `~`, `&`, `^`, `|`, `<<`, `>>`, `>>>` and their compound forms take
16
+ non-nullable `int` operands only. Enums, bools, float literals such as `1.0`,
17
+ and decimals are rejected; use `Set<Enum>` for enum membership.
18
+
19
+ ```neo
20
+ int mask = (region << 8) | (tile & 255);
21
+ mask &= ~2;
22
+ ```
23
+
24
+ Operands wrap to signed int32 and every result is signed int32. Shift counts
25
+ use their low five bits (32 means 0, -1 means 31). `>>` extends the sign;
26
+ `>>>` fills with zero but still returns signed, so `-1 >>> 1` is 2147483647.
27
+ Operands evaluate once, left to right. Write shifts without spaces
28
+ (`x >> 1`, never `x > > 1`).
29
+
30
+ Precedence, tightest first: unary, switch expressions, `* / %`, `+ -`, shifts,
31
+ comparisons, equality and `is`, `&`, `^`, `|`, `&&`, `||`, `??`, `?:`.
32
+
33
+ ## Math
34
+
35
+ `Math` has C# names and semantics: `Min`, `Max`, `Clamp`, `Round`, `Floor`,
36
+ `Ceiling`, `Truncate`, `Abs`, `Sign`, `Sqrt`, `PI`, `E`.
37
+
38
+ ```neo
39
+ return Math.Clamp(this.Quality + bonus, 0, 10);
40
+ ```
41
+
42
+ Results follow the argument types: all-`int` gives `int`, any `float` gives
43
+ `float`, any `decimal` gives `decimal`, and `decimal` never mixes with `float`.
44
+ `Round`, `Floor`, `Ceiling`, and `Truncate` are the float-to-int conversions:
45
+ they return `int` for int/float input, keep `decimal` as `decimal`, and throw
46
+ on non-finite input. `Round` rounds midpoints to even. `Sign` returns `int`;
47
+ `Sqrt` takes int or float only.
48
+
49
+ `Pow`, `Exp`, `Log`, `Log10`, `Sin`, `Cos`, `Tan`, `Asin`, `Acos`, `Atan`,
50
+ `Atan2(y, x)`, `Lerp`, `InverseLerp`, `MoveTowards`, `Repeat`, and `PingPong`
51
+ take int/float and return float, rounding int inputs to float32 first.
52
+ Decimal and nullable operands are rejected. Angles are radians. `Lerp` and
53
+ `InverseLerp` clamp to [0, 1]; a negative `MoveTowards` delta moves away;
54
+ `Repeat`/`PingPong` need finite t and a finite positive length.
55
+
56
+ `Math` is a builtin qualifier, not a value. No class, enum, parameter, or
57
+ local may shadow it, but a qualified member may be named `Math`
58
+ (`this.Math`).
59
+
60
+ ## Random
61
+
62
+ ```neo
63
+ NeoRandom.Seed = 17; // reset the shared stream
64
+ int index = NeoRandom.Range(0, items.Count);
65
+ float chance = NeoRandom.Value();
66
+ var weather = new NeoRandom(42); // independent stream; seed required
67
+ float temperature = weather.Range(-1.0, 1.0);
68
+ ```
69
+
70
+ - Shared and instance streams have the same methods. Keep an instance in a
71
+ field when calls should share its progress; constructing it inside each call
72
+ restarts from the seed.
73
+ - `Value()` includes 0 and 1. Int `Range(min, max)` excludes max; float
74
+ `Range` includes both bounds. Equal bounds consume no draw. Reversed int
75
+ bounds still include the original min and exclude the original max;
76
+ reversed float bounds swap.
77
+ - Int bounds must fit int32, float bounds must be finite, seeds must be safe
78
+ integers. Reading `Seed` returns the assigned value; every assignment
79
+ restarts the stream.
80
+ - Stream progress and reseeding last for the session only. Getters that read
81
+ `Seed` refresh after reseeding; getters that only draw cache normally.
82
+ - Stored initializers replay independently of gameplay streams. A
83
+ construction that drew random values keeps its owned values as overrides
84
+ until it is edited or rebuilt.
85
+
86
+ For repeatable runs pass a signed decimal seed: `neo test --seed 17` or
87
+ `neo script eval --seed 17 --returns float 'return NeoRandom.Value();'`. Each
88
+ test gets a fresh stream shared by its hooks and body. Without `--seed`, tests
89
+ use the project-fingerprint seed printed in the report and other sessions use
90
+ host entropy.
91
+
92
+ ## Vectors and Color
93
+
94
+ `Vector2`, `Vector3`, `Vector2Int`, and `Vector3Int` support `+`, `-`, unary
95
+ `-`, scalar multiply in either order, and scalar divide. Both vector operands
96
+ must be the same type. Int vectors take only int scalars and truncate on
97
+ division; float vectors take int or float. Division by zero throws. `==` is
98
+ exact component equality. Int vectors never convert implicitly to float
99
+ vectors.
100
+
101
+ ```neo
102
+ Vector2 heading = (target - position).normalized;
103
+ Vector2 step = Vector2.MoveTowards(position, target, speed);
104
+ ```
105
+
106
+ Every vector has read-only `magnitude` and `sqrMagnitude`, static `Distance`,
107
+ `Min`, `Max`, and constants `zero`, `one`, `up`, `down`, `left`, `right` (3D
108
+ adds `forward`, `back`). Float vectors add `normalized`, `Dot`, `Lerp`, and
109
+ `MoveTowards`; `Vector3` adds `Cross`. `normalized` returns zero at magnitude
110
+ 0.00001 or less.
111
+
112
+ `Color.Lerp(a, b, t)` interpolates all four channels. Vector and Color `Lerp`
113
+ clamp t to [0, 1]. Arithmetic results and constants are fresh values;
114
+ component writes keep the ordinary vector aliasing rules.
@@ -0,0 +1,113 @@
1
+ # NeoScript
2
+
3
+ NeoScript is C#-shaped, statically typed, and sandboxed. This page holds the
4
+ rules every body follows. Open a leaf from [References](#references) only when
5
+ the task uses that API.
6
+
7
+ ## Bodies live inline
8
+
9
+ Write getters, setters, functions, constructors, dialogue logic, and group
10
+ logic inline in their owning `.neo` or `.neoflow` declaration. Never create
11
+ `Scripts/<Class>/<Member>.neo` sidecars.
12
+
13
+ ```neo
14
+ public bool HasVisited => this.VisitCount > 0;
15
+
16
+ public int Clamped {
17
+ get => Math.Clamp(this.Raw, 0, 10);
18
+ set(int value) { this.Raw = value; }
19
+ }
20
+
21
+ public bool Unlocked(int level) => this.Level >= level;
22
+ ```
23
+
24
+ - `=> expr;` is sugar for a one-`return` block, on getter-only properties,
25
+ accessors, and functions. In a void context it must call or assign
26
+ (`void Ping() => this.OnUpdated();`). Pull writes one-line single returns as
27
+ `=>` and keeps setters as blocks; push keeps whichever form you wrote.
28
+ - `=>` after an `=` initializer is a NeoFlow destination, not a body.
29
+ - A property has at most one getter and one setter. Setters are
30
+ `set(int value) { ... }` with the property type and the name `value`; an
31
+ empty body is a valid no-op.
32
+ - Bodyless members must be `abstract` (`public abstract int Count { get; }`,
33
+ even in an abstract class), `native` (host-provided only), or interface
34
+ contracts. `async` alone is not enough. Use a field such as
35
+ `public int Count = 0;` for stored data.
36
+ - In an override, `base.Member(args)` or `base.Property` calls the nearest
37
+ ancestor implementation with the same `this`. `base` outside an override,
38
+ on an abstract member, for base constructors, or for stored-field reads is
39
+ an error.
40
+
41
+ ## Names and calls
42
+
43
+ - Inside a class body, unqualified names resolve against that class.
44
+ - Calls may be statements even when they return a value; the result is
45
+ discarded (`this.Items.Remove(item);`).
46
+ - `new MissingClass(5)` needs a declared type everywhere. Contextual `new(...)`
47
+ uses the expected type.
48
+ - An error inside a referenced initializer is reported at that initializer's
49
+ own file and position. Fix it there, not at the `Reference(...)` use.
50
+
51
+ ## Nullability
52
+
53
+ Never access a nullable receiver directly. Narrow with a condition, or use
54
+ `?.`, `??`, or `!` when failure is intended:
55
+
56
+ ```neo
57
+ if (player.Target != null) {
58
+ return player.Target.Name;
59
+ }
60
+ return player.Target?.Name ?? "Unknown";
61
+ ```
62
+
63
+ Narrowing details, `is` patterns, and interface guards are in
64
+ [types and narrowing](types-and-narrowing.md).
65
+
66
+ ## Identity and writes
67
+
68
+ - `==`, `Contains`, `IndexOf`, and `Remove` compare classes, interfaces, and
69
+ collections by identity, and primitives, enums, vectors, and media by value.
70
+ - Runtime ownership decides whether a write is allowed: Immutable, Save,
71
+ Session, setter, or otherwise writable. The body kind does not.
72
+ A constructor writes only its own `this`. A structured-leaf field inherits
73
+ its receiver's storage.
74
+ - Object initializers work in bodies and specs:
75
+ `var tile = new DirtWetTile { Cell = Vector2Int(3, -2) };`. Entries that
76
+ settle required members run with construction; the rest are ordinary
77
+ assignments in source order.
78
+
79
+ ## Check your work
80
+
81
+ Run `neo script check --all` or `neo test` after NeoScript edits. Both build
82
+ the prospective project from local source, so new code needs no push first.
83
+
84
+ ## References
85
+
86
+ Read only the leaf the task needs:
87
+
88
+ - [Math and random](math-and-random.md): numeric types, bitwise operators,
89
+ `Math`, `NeoRandom` streams and seeds, vector and Color math.
90
+ - [Strings](strings.md): string members, `Parse`/`TryParse`, number formats.
91
+ - [Collections](collections.md): LINQ-style queries, reductions, ordering,
92
+ `Insert`/`Remove`/`Sort`, `List.Repeat`, membership equality.
93
+ - [Control flow](control-flow.md): `?:`, loops, dictionary deconstruction,
94
+ switch statements and expressions, try/catch/finally, error types.
95
+ - [Types and narrowing](types-and-narrowing.md): null and type narrowing,
96
+ patterns, `Equals`, generic functions and inference, tuples.
97
+ - [Delegates and listeners](delegates-and-listeners.md): lambdas, method
98
+ groups, `NeoAction`, `OnChanged` subscriptions, `@listeners`.
99
+ - [Checking, testing, and migrations](testing-and-migrations.md):
100
+ `neo script`, `neo test`, `Debug`, `neo migrate`.
101
+
102
+ Related branches:
103
+
104
+ - [References](../values/references.md): `Reference<T>` handles and slots.
105
+ - [Extensions](../declarations/extensions.md): typed receiver functions and computed properties.
106
+ - [Overloads](../declarations/overloads.md): declaring and calling overloads.
107
+ - [Constructors](../declarations/constructors.md) and
108
+ [initializers](../declarations/initializers.md): construction order and
109
+ what member defaults may read.
110
+ - [Dialogues](../dialogues.md#triggering-dialogues-from-neoscript):
111
+ `TryTrigger`, `CanTrigger`, visit counts.
112
+ - [Grids and cell patterns](../world/grids-and-cell-patterns.md):
113
+ `GetObjects`, `GetTiles`, `GetArea`.
@@ -0,0 +1,47 @@
1
+ # Strings, parsing, and formatting
2
+
3
+ ## String members
4
+
5
+ Strings have `Length`, `Substring(startIndex[, length])`, `IndexOf`,
6
+ `LastIndexOf`, `Replace(search, replacement)`, `Split(separator)`,
7
+ `PadLeft`/`PadRight(totalWidth[, paddingChar])`, `Trim`, `TrimStart`, and
8
+ `TrimEnd`, plus statics `string.Join(separator, values)`,
9
+ `string.IsNullOrEmpty`, and `string.IsNullOrWhiteSpace`.
10
+
11
+ - Lengths and indices count UTF-16 code units. Every comparison is ordinal and
12
+ case-sensitive. Whitespace is C#'s `char.IsWhiteSpace` set.
13
+ - `Replace` replaces every non-overlapping match and returns a new string. The
14
+ search must be nonempty and neither argument may be null.
15
+ - `Split` keeps empty entries and returns a new list.
16
+ - `Join` takes `List<string>` or `Set<string>`. Convert other entries first:
17
+ `values.Select(v => $"{v}")`.
18
+ - `s?.Length` is an error. Use `string.IsNullOrEmpty(s)` or narrow first.
19
+
20
+ ```neo
21
+ List<string> parts = this.Text.Trim().Split(" ");
22
+ int quantity = int.TryParse(parts[1]) ?? 0;
23
+ return $"{slot.ToString("D3")} {price.ToString("N2")}";
24
+ ```
25
+
26
+ ## Parsing
27
+
28
+ `int.Parse`, `float.Parse`, and `decimal.Parse` accept ASCII digits with an
29
+ optional sign (plus a fraction for float/decimal and an exponent for float)
30
+ after trimming ASCII whitespace. They throw on anything else or on a value the
31
+ type can't hold; `TryParse` returns `null` instead. `decimal.Parse` never
32
+ rounds: more than 28 significant or fractional digits is an error.
33
+
34
+ ## Formatting
35
+
36
+ Numbers format only with an explicit format: `D<n>` (int only, zero-padded),
37
+ `F<n>` (fixed point), or `N<n>` (fixed point with `,` grouping). `n` is 0 to
38
+ 28 and defaults to 2 (1 for `D`). Float rounds half to even from its exact
39
+ value, decimal rounds half away from zero, and an all-zero result has no `-`.
40
+ For plain text, interpolate: `$"{value}"`.
41
+
42
+ ## Int-to-float conversion
43
+
44
+ An int assigned to `float` rounds to float32. This applies to arguments,
45
+ returns, collection entries, `List.Repeat` with a float entry type, and int
46
+ search arguments to `Contains`/`IndexOf` on float collections. Nullable
47
+ assignments keep `null`.
@@ -0,0 +1,73 @@
1
+ # Checking, testing, debugging, and migrations
2
+
3
+ ## Script commands
4
+
5
+ ```sh
6
+ neo script check --all
7
+ neo script check --this Outpost --returns string 'return $"{this.Name}!";'
8
+ neo script check --mode setter --member ComputedName 'root.Session.Name = value;'
9
+ neo script check --mode nsfunction --member Outpost.RefreshUnlock 'return this.Level > 0;'
10
+ neo script compile --mode nsfunction --member Outpost.RefreshUnlock 'return this.Level > 0;'
11
+ neo script eval --returns string 'return root.Assets.Outposts[0].FullDisplayText;'
12
+ neo script eval --function Outpost.RefreshUnlock --this-value <id> --args '[3]'
13
+ neo script eval --function "Inventory.Add(Item, int)"
14
+ ```
15
+
16
+ Prefer `--json` in automation. `check --all` rebuilds the prospective project
17
+ from local source only, including dialogue, migration, and text-variable
18
+ bodies, so new declarations need no prior push or network fetch. `eval` uses
19
+ authored values and the web evaluator, reports effects, and never commits
20
+ them. A signature selects one overload of an overloaded function.
21
+
22
+ ## neo test
23
+
24
+ `neo test` compiles the complete pending candidate, including idless nested
25
+ construction rows, and validates the graph. Tests must pass before the first
26
+ push; do not add IDs or push to get compiled initializer bodies. Invalid
27
+ production source stops the run. It skips the extra source round trip that
28
+ `neo push --dry-run` performs.
29
+
30
+ - Specs live in colocated `*.spec.neo` files that never sync. Object
31
+ initializers work there as in bodies.
32
+ - Files run in parallel on up to four CPU workers. Set `test.maxWorkers` in
33
+ `neo.json`, pass `--maxWorkers <n>`, or `--runInBand`. Hook and test order
34
+ within a file is preserved; fixtures and mocks stay isolated.
35
+ - `--seed <n>` replays `NeoRandom`; see [math and random](math-and-random.md#random).
36
+ - Unchanged project and spec inputs reuse local caches. JSON
37
+ `timings.executeMs` and `timings.workerStartupMs` give elapsed phase times;
38
+ summed worker time can exceed them.
39
+ - Mock or spy on an overloaded function by naming its delegate type:
40
+ `mock.function<NeoDelegate<int, Item, int>>(Pricing.Quote)`.
41
+
42
+ ## Debug output
43
+
44
+ `Debug.Log`, `Debug.LogWarning`, and `Debug.LogError` are void statements.
45
+ `Debug.Assert(condition, message)` logs when the condition is false. None of
46
+ them throw. Assert evaluates both arguments every time, even when a host sink
47
+ is disabled.
48
+
49
+ ```neo
50
+ Debug.Assert(this.Items.Any(), "Expected at least one item");
51
+ ```
52
+
53
+ Messages carry NeoScript line/column locations and stack frames when source
54
+ metadata exists. The CLI logs to stderr so JSON stdout stays parseable.
55
+ Getter re-evaluation can log again. `Debug` is a builtin qualifier; an
56
+ authored local or class named `Debug` wins.
57
+
58
+ ## Migrations
59
+
60
+ Migrations are tracked action files under `Migrations/`:
61
+
62
+ ```sh
63
+ neo migrate new <name> --target <ClassName|project>
64
+ neo migrate list
65
+ neo migrate check
66
+ neo migrate run [--dry-run] [--skip-invalid]
67
+ neo migrate prune
68
+ ```
69
+
70
+ Run `check` first. Review storage ownership and every write; compiling does
71
+ not make an action writable. `run` reads the project from the server and
72
+ prepares writes locally like push. If another commit lands meanwhile, it is
73
+ refused; run it again.