@neocompose/cli 0.87.1 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +14 -0
- package/dist/neo-runtime.mjs +3789 -1141
- package/dist/test-worker.mjs +2450 -116
- package/package.json +1 -1
- package/skills/neocompose-cli/SKILL.md +95 -279
- package/skills/neocompose-cli/references/cli-development.md +101 -117
- package/skills/neocompose-cli/references/declarations/annotations-and-storage.md +112 -0
- package/skills/neocompose-cli/references/declarations/constructors.md +164 -0
- package/skills/neocompose-cli/references/declarations/declarations.md +103 -0
- package/skills/neocompose-cli/references/declarations/initializers.md +100 -0
- package/skills/neocompose-cli/references/declarations/member-kinds.md +107 -0
- package/skills/neocompose-cli/references/declarations/overloads.md +50 -0
- package/skills/neocompose-cli/references/dialogues.md +145 -0
- package/skills/neocompose-cli/references/neoscript/collections.md +98 -0
- package/skills/neocompose-cli/references/neoscript/control-flow.md +123 -0
- package/skills/neocompose-cli/references/neoscript/delegates-and-listeners.md +107 -0
- package/skills/neocompose-cli/references/neoscript/math-and-random.md +114 -0
- package/skills/neocompose-cli/references/neoscript/neoscript.md +112 -0
- package/skills/neocompose-cli/references/neoscript/strings.md +47 -0
- package/skills/neocompose-cli/references/neoscript/testing-and-migrations.md +73 -0
- package/skills/neocompose-cli/references/neoscript/types-and-narrowing.md +111 -0
- package/skills/neocompose-cli/references/sync/branches-and-history.md +69 -0
- package/skills/neocompose-cli/references/sync/conflicts-and-recovery.md +68 -0
- package/skills/neocompose-cli/references/sync/sync.md +96 -0
- package/skills/neocompose-cli/references/sync/unity-export.md +47 -0
- package/skills/neocompose-cli/references/values/defaults-and-localization.md +86 -0
- package/skills/neocompose-cli/references/values/files.md +71 -0
- package/skills/neocompose-cli/references/values/identities.md +97 -0
- package/skills/neocompose-cli/references/values/references.md +151 -0
- package/skills/neocompose-cli/references/values/values.md +110 -0
- package/skills/neocompose-cli/references/world/animation.md +133 -0
- package/skills/neocompose-cli/references/world/grids-and-cell-patterns.md +55 -0
- package/skills/neocompose-cli/references/world/variants.md +112 -0
- package/skills/neocompose-cli/references/world/world.md +105 -0
- package/skills/neocompose-cli/references/animation-and-world-authoring.md +0 -582
- package/skills/neocompose-cli/references/commands-and-sync.md +0 -408
- package/skills/neocompose-cli/references/declarations-and-construction.md +0 -773
- package/skills/neocompose-cli/references/neoscript.md +0 -990
- package/skills/neocompose-cli/references/values-identities-and-references.md +0 -421
|
@@ -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.
|