@neocompose/cli 0.87.2 → 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 +12 -0
  2. package/README.md +14 -0
  3. package/dist/neo-runtime.mjs +3288 -724
  4. package/dist/test-worker.mjs +2445 -114
  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,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,112 @@
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
+ - [Overloads](../declarations/overloads.md): declaring and calling overloads.
106
+ - [Constructors](../declarations/constructors.md) and
107
+ [initializers](../declarations/initializers.md): construction order and
108
+ what member defaults may read.
109
+ - [Dialogues](../dialogues.md#triggering-dialogues-from-neoscript):
110
+ `TryTrigger`, `CanTrigger`, visit counts.
111
+ - [Grids and cell patterns](../world/grids-and-cell-patterns.md):
112
+ `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.
@@ -0,0 +1,111 @@
1
+ # Types, narrowing, and tuples
2
+
3
+ Null and type narrowing, patterns, equality, generic functions, and tuples.
4
+
5
+ ## Null narrowing
6
+
7
+ - Null propagation continues through later required accesses in the same
8
+ chain: `config.Hat?.Head.Idle.Down.Frames` is null when `Hat` is null. A
9
+ nullable descendant still needs its own `?.`, `??`, `!`, or guard.
10
+ - `Member` and `this.Member` share narrowing. A local or parameter named
11
+ `Member` shadows the member; `this.Member` still names the field.
12
+ - Reassigning a narrowed path to a nullable value clears its facts and its
13
+ descendants' facts. Writes to unrelated members keep guards, also in loops.
14
+ A false earlier condition narrows later `else if` conditions.
15
+ - `a ?? b` is non-null when `b` is non-null, including in constructor
16
+ arguments such as `new Sprite(item.Sprite ?? SpriteInfo.Empty)`.
17
+ - Where a reference is expected, `first ?? fallback` picks the reference; a
18
+ present reference whose target is null does not select the fallback. In a
19
+ scalar context the reference reads its target first.
20
+ - Lookup results follow the same rules.
21
+
22
+ ## Type patterns
23
+
24
+ ```neo
25
+ if (item is not BedItem bedItem) {
26
+ throw "Expected a bed item";
27
+ }
28
+ return bedItem.Name;
29
+ ```
30
+
31
+ - A binding stays in scope after a guard when every path that falls through
32
+ matched, including when other paths return from a nested block.
33
+ - `value is not T name` binds only where the test is known false: the `else`
34
+ branch, the right of `||`, or after a rejecting guard. The operand evaluates
35
+ once. `not` is an ordinary identifier elsewhere.
36
+ - Patterns follow the possible runtime type. An open class can match an
37
+ unrelated interface through a subclass that implements it; a sealed class
38
+ without it is a compile-time impossible pattern. A class implements each
39
+ interface once, so a value known to be `IBox<int>` never passes
40
+ `is IBox<string>`.
41
+ - An interface guard such as `if (item is not IReceiver) continue;` narrows
42
+ `item` itself: it keeps its declared class or generic `T` members and gains
43
+ `IReceiver` members. Later class guards accumulate (`IReceiver` then
44
+ `Furnace` exposes both). Guards that return, throw, or break work the same.
45
+ - A declaration pattern keeps the original receiver's type too: after
46
+ `if (item is not IReceiver receiver) return;`, `item` is unchanged and
47
+ `receiver` exposes `IReceiver`.
48
+ - Generic `is` tests check whether the bound can overlap the tested type.
49
+ - Guards also narrow named globals and static member paths, qualified or not,
50
+ inherited included. Assignment clears the proof. Closures must guard again
51
+ because the stored value can change before they run.
52
+ - A generic receiver may call members of its class or interface constraint;
53
+ nullable receivers still need narrowing.
54
+
55
+ ## Equality
56
+
57
+ `==`, `!=`, `Contains`, `IndexOf`, and `Remove` compare class, interface,
58
+ List, Set, and Dictionary values by identity. Primitives, enums, vectors, and
59
+ media references compare by value. The static type decides, so an
60
+ unconstrained `T` compares by value.
61
+
62
+ `Equals(other)` on a non-null generic value calls the runtime class's
63
+ one-argument `bool Equals` (including overrides) when it has one, otherwise
64
+ falls back to value equality. Local sets fall back to comparing current
65
+ entries in insertion order.
66
+
67
+ ## Generic functions
68
+
69
+ Functions, natives, and interface functions may declare type parameters with
70
+ optional constraints and defaults:
71
+
72
+ ```neo
73
+ public static T? FirstOf<T extends Item = Item>(List<T> items) => items.FirstOrDefault();
74
+ ```
75
+
76
+ - Call with every type argument, omit a defaulted tail
77
+ (`FirstOf<Seed>(items)`), or omit `<...>` to infer from arguments.
78
+ - Lambda inputs bind first so their returns can infer outputs, as in
79
+ `Map(seed, (value) => value.Price)`. Defaults fill parameters with no
80
+ evidence. The expected return type is not evidence. Explicit `<...>`
81
+ never infers the omitted tail. Conflicting types need explicit arguments;
82
+ inference does not invent a common ancestor.
83
+ - Function type parameters take no `@id`. Overrides declare the same count,
84
+ constraints, and defaults but may rename them.
85
+
86
+ Generic class arguments are invariant through inheritance: `IntBox : Box<int>`
87
+ assigns to `Box<int>`, never `Box<string>`. Typed locals accept generic
88
+ classes (`Box<int> box = new(1);`). Explicit lambda parameter types accept
89
+ the full declaration grammar, including `decimal`, generics, collections, and
90
+ nullables.
91
+
92
+ ## Tuples
93
+
94
+ ```neo
95
+ (int Row, int Col) position = (Row: 2, Col: 3);
96
+ var (row, col) = position;
97
+ (int first, (bool enabled, int _)) = (1, (true, 9));
98
+ var score = positions.Select(((r, c)) => r + c);
99
+ ```
100
+
101
+ - Two or more elements. Labels are optional and don't affect type identity;
102
+ `Item1`, `Item2`, ... always work. Slots are immutable: replace the tuple.
103
+ - `((r, c)) =>` takes one tuple; `(r, c) =>` takes two parameters. Recursive
104
+ patterns work in declarations, loop bindings, and lambda parameters. `_`
105
+ discards. Assignment deconstruction is unsupported.
106
+ - Equality is recursive by element; decimal scale is ignored, classes and
107
+ collections compare by identity.
108
+ - `(int, int)?` is a nullable tuple; `(int?, int)` has a nullable element.
109
+
110
+ Stored tuple members are covered in
111
+ [member kinds](../declarations/member-kinds.md#stored-tuples).
@@ -0,0 +1,69 @@
1
+ # Branches, releases, and access
2
+
3
+ ## Branches and releases
4
+
5
+ ```sh
6
+ neo branch list
7
+ neo branch create <name> [--from <ref>]
8
+ neo branch switch <nameOrId>
9
+ neo branch refresh [--dry-run]
10
+ neo merge <branch> [--dry-run] [--migrate]
11
+ neo release cut [--bump major|minor|patch] [--dry-run]
12
+ ```
13
+
14
+ - Branches are copy-on-write forks; releases are immutable snapshots.
15
+ - Switching needs a clean checkout. Unpushed source or binaries, conflicts,
16
+ source errors, and pending push recovery stop it before anything changes.
17
+ Push or back up first; `neo pull --force` only when discarding is intended.
18
+ - Creation waits until the durable copy publishes. The source stays readable
19
+ while copying.
20
+ - The server derives the minimum compatibility bump; a caller may only raise
21
+ it. Releases need the `release` login profile.
22
+ - In Unity projects using `unityConfigPath`, branch and version switches
23
+ update `NeoComposeConfig.asset`.
24
+
25
+ ## History
26
+
27
+ ```sh
28
+ neo history inspect
29
+ neo history log
30
+ neo history prune [--project <id|@latest|@current>]
31
+ neo history flatten [--project <id|@latest|@current>] --version <id|@latest|@current>
32
+ neo history apply --plan <plan-id> [--confirm-scope <project-id>[/<version-id>]]
33
+ neo history status --run <run-id>
34
+ neo history resume --run <run-id>
35
+ ```
36
+
37
+ Rewrites need project owner or admin. `prune` and `flatten` only create
38
+ immutable plans. `apply` starts the destructive run once the plan seals;
39
+ production also needs the exact scope confirmation the plan shows.
40
+
41
+ ## Login and credentials
42
+
43
+ ```sh
44
+ neo login [--api <url>] [--profile editor|release] [--save-project <id>]
45
+ neo whoami
46
+ neo logout [--api <url>]
47
+ ```
48
+
49
+ - Inside a workspace these use its `apiBaseUrl`; `--api` wins; outside, the
50
+ build's default origin. Login prints the origin before storing anything.
51
+ - Tokens use the OS credential store, falling back to a protected file. In CI
52
+ use `NEO_COMPOSE_TOKEN` or `--token-stdin`. The CLI refreshes its
53
+ short-lived JWT during long commands.
54
+ - The editor profile can't publish releases; server scopes are the security
55
+ boundary.
56
+ - `logout` deletes the credential for exactly one origin and revokes the
57
+ session best-effort.
58
+ - `NEO_COMPOSE_CONFIG_HOME` (absolute path replacing
59
+ `$XDG_CONFIG_HOME/neo-compose`) and `NEO_COMPOSE_CREDENTIAL_NAMESPACE`
60
+ (keychain account scope) isolate concurrent environments on one OS user.
61
+
62
+ ## Automation
63
+
64
+ - Pass explicit project/version IDs and flags, prefer `--json`, and never
65
+ depend on pickers or confirms. Non-TTY runs never wait for them.
66
+ - Boolean flags are bare switches; `--dry-run=true` is rejected.
67
+ - `--dry-run` exists on push, merge, refresh, branch refresh, release cut, and
68
+ migrate run; elsewhere it is rejected before any action. `branch create`
69
+ has none.