@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,68 @@
1
+ # Conflicts and recovery
2
+
3
+ ## Conflicts
4
+
5
+ Pull merges disjoint fields and children, and merges collection and graph
6
+ children by ID, never by index. Conflicting scalars, edges, order,
7
+ delete/edit pairs, and binaries become explicit conflicts whose source fails
8
+ compilation on purpose.
9
+
10
+ - Edit the source to the intended final state, or run `neo resolve --mine` or
11
+ `--theirs` for a deliberate whole-side choice. Then pull, review, dry-run,
12
+ and push. There is no force-CAS path.
13
+ - Either side adopts the server as the new base for the conflicts it
14
+ settles: `--theirs` then reads clean, `--mine` reads as an ordinary local
15
+ update.
16
+ - One record is one conflict however many files declare it. `neo resolve`
17
+ settles all of its files together and refuses a partly resolved record.
18
+ - Resolution keeps the chosen lines and their line endings (LF, CRLF, or
19
+ mixed). Choosing a deleted side removes the file; deletion placeholders are
20
+ display text. Empty and comment-only files stay files.
21
+ - Against a server-side deletion, keeping the record restores it as a create
22
+ guarded by an absent-record check; removing it accepts the deletion.
23
+ - Pull warns when a conflicted record's local base strands newly downloaded
24
+ descendant rows. Resolve it with `--mine` or `--theirs` before pushing.
25
+ - Compiled initializer IR and other server-derived metadata never cause a
26
+ conflict by themselves.
27
+ - Binary conflicts keep local bytes; the server copy is under
28
+ `.neo/conflicts/files/<file-id>/`.
29
+
30
+ `value-base-desync` means the workspace base no longer names value rows its
31
+ own files project, so a plan would delete live rows. Nothing is planned; run
32
+ `neo pull` and retry. As a warning, it reports without blocking.
33
+
34
+ ## Interrupted pushes
35
+
36
+ If a push committed but rewriting source failed, `neo pull` resumes the saved
37
+ result before checking the remote head. Status and push require that
38
+ recovery first. Interrupted file writes resume without parsing a half-written
39
+ checkout. If a file changed again after the plan was saved, recovery stops and
40
+ names it; save that edit separately, restore the file to its pre-rewrite or
41
+ generated content, then run `neo pull`.
42
+
43
+ ## .neo/state.json
44
+
45
+ It holds the merge baseline, source placements, and the recovery journal.
46
+ Keep it. Normal pull refuses existing source without a baseline. To replace a
47
+ checkout whose state is gone, back it up and run `neo pull --force`, which
48
+ recovers custom paths from the remaining declarations and replaces local
49
+ source with the server version.
50
+
51
+ ## neo doctor
52
+
53
+ `neo doctor` checks format, compiler, extension cache, source, files, and the
54
+ last pull's document integrity, and fails on malformed records. Pull first to
55
+ audit the current head.
56
+
57
+ - The partition audit follows ownership: a lookup may reference a value in a
58
+ different partition.
59
+ - It also reports base rows that no file projects and no record names. These
60
+ are invisible to `neo status`.
61
+ - `--json` includes a `repair` entry per finding for targeted repair.
62
+ - `--fix` repairs only `unprojected-value-row` findings: it lists each orphan
63
+ root with its row count, confirms, and deletes them in batches. Each row
64
+ carries the last pull's CAS base and each transaction expects the previous
65
+ head, so a stale audit fails instead of deleting a row that gained an
66
+ owner; a workspace with no recorded head is refused. `--yes` skips the
67
+ prompt. `--fix` has no `--json` form.
68
+ - After a reviewed `--fix`, pull and run doctor again.
@@ -0,0 +1,96 @@
1
+ # Sync commands
2
+
3
+ What `pull`, `status`, `diff`, and `push` do beyond the workflow in
4
+ [SKILL.md](../../SKILL.md). Open a leaf from [References](#references) for
5
+ conflicts and recovery, branches and releases, or Unity export.
6
+
7
+ ## Pull
8
+
9
+ - Normal pull fetches only changed records and merges them three-way by
10
+ record identity. If the remote head is unchanged it leaves source alone
11
+ without compiling; use `neo status` for local diagnostics.
12
+ - It skips records whose hashes match the saved baseline, including echoes of
13
+ this checkout's last push, and refreshes records whose remote change touches
14
+ only compiler-owned bodies or timestamps without rewriting authored files.
15
+ - It keeps declaration placement, spelling, comments, and file grouping by
16
+ stable ID. New server declarations use the default layout; system
17
+ declarations default to a `System` subfolder under their category.
18
+ Ownership comes from the reserved system identity, not the name.
19
+ - `--reset` regenerates canonical source and managed binaries, keeping
20
+ recorded declaration paths. `--force` discards local edits for the server
21
+ version. Both bypass push recovery. Use them only when that loss is
22
+ intended.
23
+ - `--regenerate-source-names` replaces generated dialogue symbols with
24
+ readable names and rewrites references.
25
+ - Pull refuses a baseline holding a compiled body this CLI rejects and names
26
+ the record. If pull can't refresh it, back up local edits and run
27
+ `neo pull --force`.
28
+
29
+ ## Status and diff
30
+
31
+ `neo status` and `neo diff` show every pending change, grouped by owning
32
+ source, including newly authored value seeds. Diagnostics carry
33
+ file:line:column, such as invalid constructor parameter types. A failed class
34
+ or constructor declaration blocks candidate preparation.
35
+
36
+ Deleting a class or collection declaration also deletes its hidden entry
37
+ members, nested lists and dictionaries included; replacing a collection with
38
+ a scalar deletes abandoned entries. Entries used by surviving declarations
39
+ stay. If you deleted a member before pulling remote instance values, pull
40
+ keeps them in the deletion plan. Review these derived deletions before
41
+ pushing.
42
+
43
+ ## Dry-run
44
+
45
+ `neo push --dry-run` rehearses the whole commit locally: source emission,
46
+ pending-ID assignment, transport shapes, hashes, repeat verification,
47
+ compilation, schema-commit materialization, seeded structural rows, and
48
+ whole-graph validation. It never prompts, uploads, commits, or rewrites
49
+ source. Pending getters and functions compile before initializers evaluate,
50
+ even on a first push, and initializers resolve pending dependencies first;
51
+ cycles report the participating values. It also checks derived-edge extractor compatibility against metadata
52
+ from the last pull; run `neo pull` if that metadata is missing.
53
+
54
+ A clean dry-run doesn't guarantee a clean push. The server still checks the
55
+ live head, per-change CAS bases, authorization, row shapes, file staging, and
56
+ admission.
57
+
58
+ ## Push
59
+
60
+ - Push recompiles only bodies affected by changed declarations, prepares the
61
+ transaction once before confirmation, and reuses it after.
62
+ `--force-recompile` (with or without `--dry-run`) sweeps every body; use it
63
+ only to diagnose stale derived IR.
64
+ - One accepted push atomically commits records, main-locale text, bindings,
65
+ and staged file metadata. Pending IDs and source rewrites become real only
66
+ after acceptance.
67
+ - Normal push hashes the authored source it read and does not re-lower the
68
+ whole tree. A lowering mismatch therefore surfaces on the next canonical
69
+ pull; dry-run is the full source-to-record proof.
70
+ - `non-fast-forward`: someone committed after your pull. Pull, then push.
71
+ - `base-hash-conflict`: pull and resolve.
72
+ - `version-bump-required`: inspect the classification and pass
73
+ `--accept-bump` only when intended.
74
+ - Completion arrives through a live subscription, with progress every five
75
+ seconds. A disconnect doesn't cancel the durable job.
76
+
77
+ ## Output and diagnostics
78
+
79
+ Interactive commands show phase spinners; `NO_COLOR` removes color only. JSON
80
+ and piped output contain no animation. Set `NEO_COMPOSE_TRACE_PERFORMANCE=1`
81
+ to write `neo-performance` JSON spans to stderr (`startedMs` relative to the
82
+ process, `durationMs` per operation; nested spans overlap, so don't sum
83
+ them).
84
+
85
+ ## References
86
+
87
+ - [Conflicts and recovery](conflicts-and-recovery.md): merge conflicts,
88
+ `neo resolve`, `value-base-desync`, interrupted pushes, `.neo/state.json`,
89
+ `neo doctor` and `--fix`.
90
+ - [Branches, releases, and access](branches-and-history.md): branches, merge,
91
+ releases, history rewrites, login profiles, tokens, flags.
92
+ - [Unity export](unity-export.md): `neo export`, `neo dev`, local exports,
93
+ workspace lock.
94
+ - [Testing](../neoscript/testing-and-migrations.md): `neo test`,
95
+ `neo script`, `neo migrate`.
96
+ - [Localization](../values/defaults-and-localization.md#neo-loc): `neo loc`.
@@ -0,0 +1,47 @@
1
+ # Unity export
2
+
3
+ `neo export` builds the Unity export from the working copy, offline, into the
4
+ directories the Unity project's `NeoComposeConfig` asset names: `project.json`
5
+ with a partition index, `Partitions/*.json` value rows, localization files,
6
+ and generated C# under `<generatedTypesDirectory>/Generated/`.
7
+ `Library/NeoCompose/export.json` is written last for Unity to ingest.
8
+
9
+ - Only changed files are written. Codegen is skipped when schema inputs are
10
+ unchanged, but content is still validated. Repeated exports, undos, and
11
+ reverts reuse cached results after verifying output bytes.
12
+ - The Unity project comes from `unityConfigPath`. Without it, an interactive
13
+ export offers to link the one config asset naming this project; scripts get
14
+ an error with the line to add. `--unity-project <dir>` exports elsewhere
15
+ without linking (agent rigs use it).
16
+ - Export stops before writing on a source error, a file conflict, a
17
+ `download` file action, an error diagnostic, or an unchanged project file
18
+ whose bytes differ from the pulled file.
19
+ - Keep `NeoGeneratedFiles.json` with the generated files; it preserves
20
+ `.meta` GUIDs across type renames.
21
+ - Export reports an error when an animation clip target doesn't match its
22
+ owner or one of its constructed base types.
23
+
24
+ Status and export stay clean when animation defaults use initializers or
25
+ constructor arguments select collection rows through Lookups.
26
+
27
+ ## Local exports
28
+
29
+ A clean working copy exports the pulled head; diagnostic location shifts
30
+ alone don't count as local changes. Local edits, `Files/` bytes included, make
31
+ the export local: `project.json` gets `metadata.localExport`, Unity fails
32
+ player builds and keeps saves under `NeoCompose/LocalExport`, and a push or a
33
+ clean export ends it.
34
+
35
+ IDs minted for local declarations, including getters used in NeoFlow
36
+ interpolation, are kept in `.neo/export.json`'s `identityLedger`, so the next
37
+ push commits the IDs Unity already saw. A push re-exports into a Unity project
38
+ that received a local export.
39
+
40
+ ## neo dev
41
+
42
+ `neo dev` watches source, binaries, ignore rules, workspace state, and config,
43
+ then exports in batches (20 ms quiet period, capped at 250 ms). Edits during
44
+ an export go into the next batch. It never pulls or pushes.
45
+
46
+ Pull, push, branch, merge, resolve, init, and export take turns through
47
+ `.neo/workspace.lock`; a lock whose process died is taken over.
@@ -0,0 +1,86 @@
1
+ # Defaults, overrides, and localization
2
+
3
+ ## Virtual members and sparse overrides
4
+
5
+ Omitted members are virtual projections of the instance's recorded
6
+ constructor or variant and follow later edits to that source.
7
+
8
+ - Writing a member creates a sparse override with a deterministic projected
9
+ ID. Removing it from an existing initializer resets the override and
10
+ resumes tracking. Both transitions must show in `neo diff`.
11
+ - Spelling a declared default verbatim is the same as omitting it: a leaf
12
+ equal to its declaration, or a bare construction the declaration wholly
13
+ settles, mints no override. Spelling an unchanged child default such as
14
+ `FPS = 8` is a no-op even when siblings own rows. A stored row that already
15
+ spells its default becomes sparse on its next authored edit.
16
+ - Adding a different child default keeps sibling row identities.
17
+ - A stored class with no construction record still inherits literal
18
+ declaration defaults (`FoodInfo.Quality = .OneStar` fills an existing row
19
+ that stores only its price). Don't add an override to repair that. Required
20
+ fields with no applicable default remain errors.
21
+ - Defaulted members can be added without recreating existing instances.
22
+ - Editing an inline child keeps the outer construction's call-site values for
23
+ its other fields while its variant selection matches; a different variant
24
+ gives it an independent recipe.
25
+
26
+ ## Emitted defaults
27
+
28
+ A member whose default constructs its declared type emits `= new()`; a
29
+ subclass default keeps its type (`Base Value = new Derived();`). Edit the
30
+ declaration to change the shared default, or assign inside an instance
31
+ initializer to override one instance. A nullable member with no default
32
+ stays bare; an explicit null default emits `= null`.
33
+
34
+ Generic arguments are types, not defaults: a required generic member needs a
35
+ value at construction unless a member declaration supplies a default. Pull
36
+ keeps incomplete stored objects for repair; add the initializer or default
37
+ before testing or pushing. An override without a local initializer inherits
38
+ the base default; an explicit `= null` is a literal nullable default.
39
+
40
+ ## Construction rebuilds
41
+
42
+ Source is authoritative for a construction. Editing its constructor
43
+ arguments, a static's included, rebuilds it in place on push:
44
+
45
+ - the value keeps its ID and rows at unchanged positions keep theirs,
46
+ - added entries create rows and removed entries delete them,
47
+ - overrides the new construction no longer produces are deleted, so keep
48
+ intended overrides in the construction's `{ ... }` block.
49
+
50
+ Switching to another overload or shared argument rebuilds even when argument
51
+ values compare equal; owned inline arguments still compare by contents. A
52
+ construction whose class changed fails with `construction-arguments-conflict`;
53
+ delete and recreate the value instead.
54
+
55
+ ## Localized text
56
+
57
+ - Write main-locale text directly in localizable string initializers. Never
58
+ author localized-text IDs in place of prose.
59
+ - Equal main-locale text on the same declared member shares one localization
60
+ record across overrides and instances. Different members keep separate
61
+ contexts.
62
+ - Editing a class default updates inheriting instances; editing one instance
63
+ doesn't change the default. Reverting an instance to its default restores
64
+ the shared record and archives an unreferenced unique one.
65
+ - An empty string stores no localization ID. Nullability, not text length,
66
+ decides whether the member is required.
67
+
68
+ ## neo loc
69
+
70
+ Source files hold only main-locale text. Use `neo loc` for other locales,
71
+ statuses, comments, archives, and repair:
72
+
73
+ ```sh
74
+ neo loc locales
75
+ neo loc list
76
+ neo loc set <textId> <locale> "text"
77
+ neo loc status-set <textId> <locale> <statusId>
78
+ neo loc comment-set <textId> <locale> "comment"
79
+ neo loc archive <textId>
80
+ neo loc restore <textId>
81
+ ```
82
+
83
+ Batches go on stdin or `--file` as a JSON array where supported. `neo loc`
84
+ doesn't rewrite source; run `neo pull` after it (or any web edit) before
85
+ further edits or a push. A write refused because another commit landed is
86
+ run again.
@@ -0,0 +1,71 @@
1
+ # Managed files
2
+
3
+ Images (PNG, JPEG, GIF, SVG) and audio clips are declared in typed registries
4
+ and stored as tracked binaries. SVG keeps its original bytes everywhere; the
5
+ CLI never rasterizes it.
6
+
7
+ ## Registries
8
+
9
+ ```neo
10
+ ImageRegistry Images = new() {
11
+ @id("sword-image-file-id")
12
+ @settings(template: PixelArt)
13
+ NeoImage Sword = new("Files/Images/Sword.png");
14
+ }
15
+
16
+ AudioClipRegistry AudioClips = new() {
17
+ @id("sword-hit-audio-file-id")
18
+ @settings(template: SoundEffect)
19
+ NeoAudioClip SwordHit = new("Files/AudioClips/SwordHit.wav");
20
+ }
21
+ ```
22
+
23
+ - `Images.Sword.Slice(0)` is a sprite; `AudioClips.SwordHit` is a clip. A bare
24
+ `Images.Sword` is only a file ID, valid in file-valued contexts such as
25
+ `SpriteInfo.FileId`.
26
+ - Registries and binaries may sit in any non-ignored folder. Update the path
27
+ when moving a binary.
28
+ - Renaming the symbol or replacing bytes keeps the file ID when `@id` stays.
29
+ - Symbols derive from the persisted file name (`tree.cherryblossom.png`
30
+ becomes `TreeCherryblossom`). Keep the symbol aligned or set
31
+ `@settings(name: "TreeCherryBlossom.png")`. The name must end with the
32
+ binary's extension because pull rebuilds the path from it.
33
+ - Without `@settings(template: ...)`, the CLI stores the complete built-in
34
+ Unity import defaults for the file kind. Template-backed settings may hold
35
+ sparse leaf paths such as `platformSettings.standalone.maxTextureSize` or
36
+ `spriteSettings.pivot.x`; keep them so untouched settings inherit.
37
+
38
+ A declaration can live in its own file, listed by name in the registry. The
39
+ declaration carries `@id`, `@settings`, and the path, and must be listed
40
+ exactly once:
41
+
42
+ ```neo
43
+ // Values/Audio/SWORD_HIT.neo
44
+ @id("sword-hit-audio-file-id")
45
+ @settings(template: SoundEffect)
46
+ NeoAudioClip SWORD_HIT = new("Files/AudioClips/SwordHit.wav");
47
+
48
+ // Files/AudioClips.neo
49
+ AudioClipRegistry AudioClips = new() {
50
+ NeoAudioClip SwordHit = SWORD_HIT;
51
+ }
52
+ ```
53
+
54
+ ## Adding, replacing, removing
55
+
56
+ - Drop a supported binary under `Files/Images/` or `Files/AudioClips/` to
57
+ create a pending file with a provisional symbol. Status, diff, and dry-run
58
+ don't rewrite the registry; a successful push creates the record, uploads
59
+ verified bytes, and writes its declaration and ID.
60
+ - Remove the declaration to delete a file. A declaration whose binary is
61
+ missing is an error.
62
+ - Pull keeps unchanged entries, comments, and order; new remote entries
63
+ append, deleted ones are removed in place.
64
+
65
+ ## Transfers and conflicts
66
+
67
+ Pull and push compare server-verified SHA-256, byte length, and MIME type.
68
+ Pull downloads up to eight files at once, reuses matching local bytes, and
69
+ verifies every download before replacing anything; a failed transfer leaves
70
+ files intact. A diverged binary keeps local bytes and writes the server copy
71
+ under `.neo/conflicts/files/<file-id>/`.
@@ -0,0 +1,97 @@
1
+ # Identities
2
+
3
+ ## Keep, omit, or author
4
+
5
+ - Keep every existing `@id` through rename, reorder, and file moves. Renaming
6
+ a member with its ID keeps its value graph; update initializer keys to the
7
+ new name and keep child IDs.
8
+ - A copy gets a new ID.
9
+ - New records omit `@id`. A successful real push assigns and inserts it;
10
+ dry-run and failed pushes leave source unchanged.
11
+ - Never match an existing ordered row by index or payload. A bare row is a
12
+ pending create.
13
+ - Never invent IDs for rows whose identity derives from an owner role: root
14
+ and static bindings, projected values, and constructor arguments (named or
15
+ positional), whose identity comes from the schema key the parameter
16
+ settles.
17
+
18
+ Nested class and list rows keep inline IDs:
19
+
20
+ ```neo
21
+ Tags = [
22
+ @id("story-tag-item-id")
23
+ "story",
24
+ ];
25
+ ```
26
+
27
+ ## Authored UUIDs
28
+
29
+ When a same-push structural reference has no symbol, path, or unique-key
30
+ form, give the new row a never-used UUID-v4 `@id` and reference it in the same
31
+ edit. The server checks shape and uniqueness. Reusing an existing ID means
32
+ update, not create. Dry-run keeps a valid authored UUID-v4 on a new nested
33
+ member or dictionary value. A malformed unknown ID is an error at its
34
+ annotation: remove it or supply an unused UUID-v4. Prefer generated IDs.
35
+
36
+ ## Moving a nested value
37
+
38
+ A stored nested class value may keep its UUID-v4 when moved to an empty,
39
+ compatible class field on another existing parent, if all of these hold:
40
+
41
+ - the old owning declaration is removed in the same push,
42
+ - both fields declare the same non-generic class and storage rules,
43
+ - exactly one ownership path remains to the same root or static binding
44
+ through ordinary class fields, and
45
+ - both paths use inherited storage and automatic distribution, with no member
46
+ overrides.
47
+
48
+ Moves of projected values, constructor-owned slots, partitioned values,
49
+ generic instances, unordered-list entries, or into newly constructed parents
50
+ are refused. Omit the ID to create a new value there instead.
51
+
52
+ ## Local initializer IDs
53
+
54
+ An existing row ID may precede a local declaration. It names the initializer
55
+ row, not a stored global:
56
+
57
+ ```neo
58
+ @id("existing-quest-row-id")
59
+ Quest HardAsStone = new Quest() { Name = "Hard as stone" };
60
+ ```
61
+
62
+ `Quest HardAsStone = @id("...") new Quest();` is accepted and formatted to the
63
+ form above. Never put an ID in both places. A local keeps its concrete type
64
+ when a base-typed member consumes it.
65
+
66
+ ## Locals as constructor arguments
67
+
68
+ In constructor arguments and executable operands, a class local names its one
69
+ stored instance rather than building a copy. Its owning placement keeps it,
70
+ so a constructor that stores it needs a `Reference` field:
71
+
72
+ ```neo
73
+ @id("86ac7c7d-8d3b-4642-9def-cdf32643bdc5")
74
+ DeliveryQuest HARD_AS_STONE = new();
75
+ // The core quest list owns HARD_AS_STONE.
76
+ WorkflowStep UNLOCK = new QuestUnlockStep(HARD_AS_STONE);
77
+
78
+ class QuestUnlockStep(DeliveryQuest quest) : WorkflowStep {
79
+ public Reference<DeliveryQuest> Quest = quest;
80
+ }
81
+ ```
82
+
83
+ - The local needs exactly one owning placement. Unowned locals, ambiguous
84
+ placements, and cycles are source errors.
85
+ - A nested target needs its existing `@id`. A structurally owned list entry
86
+ may use an unused UUID-v4 in the same push.
87
+ - A local nested inside an evaluated constructor stays idless on first push;
88
+ push assigns its identity before it can be referenced. Don't give it a fresh
89
+ UUID.
90
+ - A direct static owner supplies a pending identity for an idless local, and
91
+ aliases of that static local work too.
92
+ - A member default is a construction template, so its local is not a single
93
+ stored instance.
94
+ - Owned locals inside evaluated initializers need directly addressable fields,
95
+ list entries, or literal dictionary keys. Inline the construction when a
96
+ computed key hides the owning path.
97
+ - Canonical construction arguments may use `Reference<Type>(id: "...")`.
@@ -0,0 +1,151 @@
1
+ # References
2
+
3
+ `Reference<T>` names one stored row. `T` may be any value type, collections
4
+ and other references included; code types such as `Action<int>` are rejected.
5
+
6
+ ## Handles and slots
7
+
8
+ - A handle has a class, interface, List, Set, or Dictionary `T`. The
9
+ reference is the object: `Reference<Item>` reads as `Item`, and assigning an
10
+ `Item` rebinds it. A handle never owns, so it can't hold a new list or a
11
+ query result.
12
+ - A slot has any other `T` (`bool`, numbers, `string`, enums, vectors, `Color`,
13
+ sprites, audio clips, `NeoVariant<T>`, `NeoDialogueReference`, or one
14
+ `Reference<U>`). It names another member and reads and writes that member's
15
+ value.
16
+
17
+ Inside a generic body, `Reference<T>` takes the category of `T`'s binding.
18
+
19
+ ## Declaring lookups
20
+
21
+ ```neo
22
+ public Reference<Config> Config; // one row, reads as Config
23
+ public Set<Reference<Config>> Configs; // several, reads as Set<Config>
24
+ public Reference<int> TrackedStat; // another member's int
25
+ ```
26
+
27
+ - `Reference<T>` also works as a parameter, collection element, dictionary
28
+ value, and unbound generic argument (`Box<Reference<int>>`), and keeps that
29
+ storage type through push and pull. Use `Set<T>` parameters for several
30
+ selections.
31
+ - `@settings(collection: Owner.Member)` binds a class reference to one
32
+ collection. Unbound references select any stored row of `T` or a subclass,
33
+ or any member slot of type `T`. A bound reference can't be a generic
34
+ argument. Settings on `T` itself are an error; the language server's quick
35
+ fix respells them.
36
+ - The lookup class must be assignable to the collection's entry class; a
37
+ subclass may narrow it. An `NPC` lookup can't select from `List<Quest>`.
38
+ - A class-constrained generic lookup (`Reference<TAsset>` with
39
+ `TAsset extends HeldItemAsset`) may bind a collection of the bound class.
40
+ Overrides that specialize the target keep the collection setting.
41
+ - A single reference's initializer body returns one row, never a set.
42
+ - `Reference<T>` satisfies interface `T Foo { get; }` or `{ get; set; }` and
43
+ abstract `T Foo { get; }`.
44
+ - Generated C# exposes a read-only interface; call `TryWritable` to write.
45
+
46
+ ## Defaults
47
+
48
+ ```neo
49
+ public Reference<Item> StarterSeed = root.Assets.Items.Index.Slug["seed.carrot"]!;
50
+ public Reference<int> Gold = Reference(root.Player.Gold);
51
+ public Reference<string> Greeting = Reference(root.Npcs.Index.Slug["mira"]!.Greeting);
52
+ public Reference<NeoVariant<Tree>> Look = Reference(root.Garden.Felled);
53
+ ```
54
+
55
+ - An unbound handle default may name its row by a unique index: a `root`
56
+ path to a List, `.Index.<Name>`, and a string literal key. Other shapes are
57
+ initializer code.
58
+ - Slot defaults wrap a member path in `Reference()`. A plain value is not a
59
+ default, and `this` doesn't exist yet. A `NeoVariant<T>` member's own
60
+ default stays the bare variant path.
61
+ - List and dictionary defaults may hold slot references, nested too:
62
+ `List<Reference<int>> Counts = [Reference(root.Save.Bits)]`. Each instance
63
+ owns the collection; entries target the original slots.
64
+
65
+ ## Source forms
66
+
67
+ ```neo
68
+ Reference(Assets.Capitol)
69
+ Reference(root.Player.Gold)
70
+ Reference<Outpost>(id: "capitol-value-id")
71
+ Reference<NeoSpriteObject>(id: "authored-child-id", withProvenance: true)
72
+ Reference<PantsAsset>(key: "pants.long")
73
+ Reference<PantsAsset>(key: "pants.long", index: Slug)
74
+ Reference<NeoDialogue>(id: "capitol-dialogue-id")
75
+ ```
76
+
77
+ - Prefer a symbol or root path. A slot always names a member path or `id:`.
78
+ - `key:` needs a unique index. Omit `index:` when there is exactly one.
79
+ - Use `id:` when the ID is all you have. Unknown named arguments are errors.
80
+ - `withProvenance: true` (executable NeoScript only, default false) selects
81
+ the nearest `sourceValueId` clone of an authored row in the receiver's
82
+ ownership graph. It is not a member-default form.
83
+ - `key:` and `id:` work in declaration defaults; a key there resolves after
84
+ every pass, so it may name a row the same push creates.
85
+ - `Reference(value)` keeps an evaluated class value's identity; it neither
86
+ clones nor wraps. An optional type argument checks the target.
87
+ - Animation and frame-override targets are selectors, not references; see
88
+ [animation](../world/animation.md#child-selectors).
89
+
90
+ ## Runtime semantics
91
+
92
+ `Reference(place)` forms a reference to a stored member: `this.Gold`,
93
+ `root.Player.Gold`, `item.Count`. Locals, parameters, list or dictionary
94
+ elements, vector components, getters, query results, and new values have no
95
+ row. When `Selected` is `Reference<Item>`, `Reference(this.Selected)` is
96
+ `Reference<Reference<Item>>`. Access checks apply where it is formed.
97
+
98
+ ```neo
99
+ this.A = 5; // writes 5 into A's target
100
+ this.A = this.Count; // writes Count's value into A's target
101
+ this.A = this.B; // A now names B's target
102
+ this.A = Reference(this.Count); // A now names Count
103
+ this.A += 1; // reads A's target, writes it back
104
+ ```
105
+
106
+ - Assigning a same-typed reference rebinds; anything else writes through.
107
+ `this.A = c ? first : second` rebinds when both arms are references, writes
108
+ when both are scalars, and reads the chosen target when mixed.
109
+ - A plain value never becomes a reference: passing `3` to a `Reference<int>`
110
+ parameter is an error.
111
+ - Constructors may initialize their own reference fields but can't write
112
+ through external targets.
113
+ - `==` compares handles by identity and slots by value. Collections of
114
+ references compare entries by target, so `Set<Reference<int>>` holds
115
+ distinct slots. Narrow `Reference<T>?` before writing through it.
116
+ - The target row decides every write: asset-owned targets error, `readonly`
117
+ targets reject writes after initialization, a Save target accepts them.
118
+ - Slot `OnChanged` listeners fire on rebinding and on target changes.
119
+
120
+ ## Same-push resolution
121
+
122
+ Declaration and file order don't matter within a push, and cyclic identity
123
+ references are legal. After the push, targets must exist (rows deleted by the
124
+ edit don't count) and satisfy assignability, membership, multiplicity, and
125
+ group constraints.
126
+
127
+ A projection may assign existing class values to lookup storage directly:
128
+
129
+ ```neo
130
+ EligibleIngredients = root.Assets.Crafting.FurnaceRecipes
131
+ .Select((MachineRecipe recipe) => recipe.Ingredient)
132
+ .ToSet()
133
+ ```
134
+
135
+ Lookup assignment still needs an existing backing row.
136
+
137
+ `collectionValue` settings name the collection itself
138
+ (`root.Assets.Quests.Daily` or `Reference(root.Assets.Quests.Daily)`) and
139
+ follow the same rules. The path may cross construction defaults and
140
+ declarations from the same push; no preliminary push or manual ID is needed.
141
+ Invalid reference settings are errors, never silently dropped.
142
+
143
+ ## Init-built objects
144
+
145
+ An object `Root.neo` writes is init-built when its class has an `init` block.
146
+ A declaration binding may name a member inside it, and push stores that
147
+ member's row. A reference captured by `init` or a constructor
148
+ (`new Rule(Reference(this.Weather))`) keeps its target's row. Status refuses a
149
+ value row's reference to a member of an init-built object (bind it from a
150
+ declaration instead) and a binding to a class or list that an `init` block
151
+ builds.