Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a22396467 | ||
|
|
21e03128a8 | ||
|
|
e5b1964f3e | ||
|
|
cffa66514b | ||
|
|
2744cff481 | ||
|
|
91052a54db | ||
|
|
7f4d87ef2c | ||
|
|
6cc0673082 | ||
|
|
ab9b003457 | ||
|
|
41d949717d | ||
|
|
69d19e501f | ||
|
|
cf68c71f05 | ||
|
|
08f0b15e1e | ||
|
|
17cb5e4291 | ||
|
|
eb9ac1e589 | ||
|
|
dae4bc19e1 |
@@ -5,4 +5,3 @@ site
|
|||||||
exports
|
exports
|
||||||
**/.aego
|
**/.aego
|
||||||
*.test
|
*.test
|
||||||
.claude
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ name: ci
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
tags: ["v*"]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -113,38 +112,3 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
/tmp/aego check examples/native-rotator
|
/tmp/aego check examples/native-rotator
|
||||||
/tmp/aego run examples/native-rotator --headless --frames 60
|
/tmp/aego run examples/native-rotator --headless --frames 60
|
||||||
|
|
||||||
release:
|
|
||||||
needs: [checks, build]
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
env:
|
|
||||||
CGO_ENABLED: "1"
|
|
||||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
GITEA_API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
- name: linux packages
|
|
||||||
run: sudo apt-get update && sudo apt-get install -y $LINUX_PKGS
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
path: .cache/native
|
|
||||||
key: native-linux_amd64-${{ hashFiles('third_party/sdl/version.txt', 'tools/bootstrap-native.sh') }}
|
|
||||||
- run: bash tools/bootstrap-native.sh
|
|
||||||
- name: windows tools
|
|
||||||
run: sudo apt-get install -y mingw-w64 nsis zip
|
|
||||||
- name: publish
|
|
||||||
env:
|
|
||||||
REQUIRE_WINDOWS: "1"
|
|
||||||
GITEA_RESOLVE: git.akarpov.ru:443:79.142.92.250
|
|
||||||
run: |
|
|
||||||
case "$GITHUB_REF" in
|
|
||||||
refs/tags/v*) sh tools/release/release.sh "$GITHUB_REF_NAME" ;;
|
|
||||||
refs/heads/master) sh tools/release/release.sh nightly ;;
|
|
||||||
esac
|
|
||||||
|
|||||||
@@ -502,12 +502,12 @@ The Stage 8 specification numbered these decisions D153, D155, D158, D159, D168
|
|||||||
|
|
||||||
| ID | Decision |
|
| ID | Decision |
|
||||||
|---|---|
|
|---|---|
|
||||||
| D289 | Web platform. `platform/web` (js/wasm, `syscall/js`) implements `platform.Platform` over one `<canvas>`: the one in `web.Options`, else `canvas[data-aego]`, else a new canvas appended to the body. The logical size is the canvas CSS box; the pixel size is CSS × `devicePixelRatio`, written back to `canvas.width/height`; a ResizeObserver (window `resize` as fallback) and a changed `devicePixelRatio` emit `EvWindowResized`; `PixelDensity` and `DisplayScale` are `devicePixelRatio`. Input: `KeyboardEvent.code` → scancode, `key` → keycode and text (no IME composition), pointer events on the canvas with pointer capture, primary pointer only (one-finger touch acts as the left button; multi-touch is Stage 18), wheel by `deltaMode` (100 px = one line, a page = 3 lines; a pixel delta that is not a whole line is precise, 10 px per unit, so smooth wheels and touchpads scroll the distance the page would. Amended 2026-10-20: it was divided by 100 too, which made such scrolling ten times slower than the browser's), focus and blur reset held state (9.2), gamepads polled through `navigator.getGamepads()` in the standard mapping while one is connected. The canvas takes keyboard focus on click; while focused it prevents the default of every key except browser shortcuts (Ctrl/Cmd combinations, F5, F11, F12). Pointer lock and fullscreen, which browsers grant only inside a user gesture, are queued and issued on the next pointer or key press; leaving them is immediate. Time is `performance.now()` and stops while the page is hidden. The loop is `platform.Driver`: `Engine.Run` hands `Step` to `requestAnimationFrame`, so VSync is always on and there is no frame limiter. Clipboard writes go to `navigator.clipboard.writeText` and an in-memory copy; reads return the copy, refreshed by `paste` events. Files go through `platform.UserFiles()` (localStorage), so profiles and saves work unchanged; the window is not a `platform.Displayer`, so display settings have nothing to apply. Translation code (scancodes, wheel, pads) lives in untagged files and is tested natively. |
|
| D289 | Web platform. `platform/web` (js/wasm, `syscall/js`) implements `platform.Platform` over one `<canvas>`: the one in `web.Options`, else `canvas[data-aego]`, else a new canvas appended to the body. The logical size is the canvas CSS box; the pixel size is CSS × `devicePixelRatio`, written back to `canvas.width/height`; a ResizeObserver (window `resize` as fallback) and a changed `devicePixelRatio` emit `EvWindowResized`; `PixelDensity` and `DisplayScale` are `devicePixelRatio`. Input: `KeyboardEvent.code` → scancode, `key` → keycode and text (no IME composition), pointer events on the canvas with pointer capture, primary pointer only (one-finger touch acts as the left button; multi-touch is Stage 18), wheel by `deltaMode` (100 px = one line, a page = 3 lines, fractional values are precise), focus and blur reset held state (9.2), gamepads polled through `navigator.getGamepads()` in the standard mapping while one is connected. The canvas takes keyboard focus on click; while focused it prevents the default of every key except browser shortcuts (Ctrl/Cmd combinations, F5, F11, F12). Pointer lock and fullscreen, which browsers grant only inside a user gesture, are queued and issued on the next pointer or key press; leaving them is immediate. Time is `performance.now()` and stops while the page is hidden. The loop is `platform.Driver`: `Engine.Run` hands `Step` to `requestAnimationFrame`, so VSync is always on and there is no frame limiter. Clipboard writes go to `navigator.clipboard.writeText` and an in-memory copy; reads return the copy, refreshed by `paste` events. Files go through `platform.UserFiles()` (localStorage), so profiles and saves work unchanged; the window is not a `platform.Displayer`, so display settings have nothing to apply. Translation code (scancodes, wheel, pads) lives in untagged files and is tested natively. |
|
||||||
| D290 | WebGL2 backend. `gpu/webgl2` implements `gpu.Device` on `WebGL2RenderingContext`, reached through `webgl2.Surface` on the window. `engine/backends_js.go` selects it, `engine/backends_native.go` selects gl33; a window with neither gets the null backend. GL enums are numeric constants; JS objects live in a table indexed by `uint32`, so `gpu/internal/glstate` (moved from gl33) caches state for both backends. Context methods are bound once; every call goes through one counter (`Device.Calls()` = calls of the last frame). WebGL2 has no sRGB default framebuffer, so the window pass renders into an SRGB8_ALPHA8 texture with a depth-stencil renderbuffer and `EndFrame` draws it to the canvas with a fullscreen triangle that encodes linear to sRGB; orientation follows D17 (the backbuffer is the window: viewport and scissor flipped, offscreen targets GL-native). Shaders: the renderer loads `engine://shaders/es/` when `Capabilities.ESSL` is set; `render/shaderx` translates the GLSL 330 sources at build time (`make shaders`, part of `make res`), a test keeps the committed copies current and validates them with glslangValidator when installed; there is no translator at run time. Timer queries use `EXT_disjoint_timer_query_webgl2` when present (a disjoint result reads as 0), otherwise the GPU lane stays empty. Context loss is handled inside the device: it keeps descriptors, shader sources and CPU copies of static buffers and textures with data, and on `webglcontextrestored` recreates every object under the same table id, so handles held above stay valid and nothing above the device reloads; render-target contents and dynamic buffers are refilled by the next frame. Uploads copy through one growing `Uint8Array`. The default build never compiles `syscall/js`: depcheck requires a js-only build constraint or a `_js` file name on every importer. |
|
| D290 | WebGL2 backend. `gpu/webgl2` implements `gpu.Device` on `WebGL2RenderingContext`, reached through `webgl2.Surface` on the window. `engine/backends_js.go` selects it, `engine/backends_native.go` selects gl33; a window with neither gets the null backend. GL enums are numeric constants; JS objects live in a table indexed by `uint32`, so `gpu/internal/glstate` (moved from gl33) caches state for both backends. Context methods are bound once; every call goes through one counter (`Device.Calls()` = calls of the last frame). WebGL2 has no sRGB default framebuffer, so the window pass renders into an SRGB8_ALPHA8 texture with a depth-stencil renderbuffer and `EndFrame` draws it to the canvas with a fullscreen triangle that encodes linear to sRGB; orientation follows D17 (the backbuffer is the window: viewport and scissor flipped, offscreen targets GL-native). Shaders: the renderer loads `engine://shaders/es/` when `Capabilities.ESSL` is set; `render/shaderx` translates the GLSL 330 sources at build time (`make shaders`, part of `make res`), a test keeps the committed copies current and validates them with glslangValidator when installed; there is no translator at run time. Timer queries use `EXT_disjoint_timer_query_webgl2` when present (a disjoint result reads as 0), otherwise the GPU lane stays empty. Context loss is handled inside the device: it keeps descriptors, shader sources and CPU copies of static buffers and textures with data, and on `webglcontextrestored` recreates every object under the same table id, so handles held above stay valid and nothing above the device reloads; render-target contents and dynamic buffers are refilled by the next frame. Uploads copy through one growing `Uint8Array`. The default build never compiles `syscall/js`: depcheck requires a js-only build constraint or a `_js` file name on every importer. |
|
||||||
| D291 | Playground runtime. One page holds the code editor, a Game View (`canvas[data-aego]`) and Console/Problems. The catalog `app/assembly/playground` is core, input, render2d, scripting, physics and debug (physics joins the playground graph, superseding D205 on that point; `render3d` stays out). Console examples (docs snippets, the donut) run headless as before. Scene examples come from `app/playground/examples`, a regular project (module `game`) embedded in the binary and checked by `aego check` in `make check-all`: its `project.aego` gives the project sections once at start, and each `scenes/<name>.aescene` with `scripts/<name>/` is one example. Run compiles the edited package in the same workspace, links a fresh script runtime and loads the scene through `sceneio` with a loader over an empty index, so only builtin assets resolve. One engine per page is built on the first Run on `platform/web` and `gpu/webgl2` and stays on `requestAnimationFrame`; Run and Stop swap the scene, the runtime and the module attachment of the main world. The page polls `poll` every 250 ms for log lines, faults and frame statistics (frame time, draw calls, WebGL calls of the last frame). The game owns the keyboard and pads while the canvas has focus, Escape included; a click elsewhere releases it. `bench` draws N sprites straight through the renderer for measurements and the screenshot golden, `loseContext` drives `WEBGL_lose_context`. Budgets are targets: `aego.wasm` ≤ 8 MB gzip (the site build fails above it), cold start ≤ 2 s on a laptop. `make playground-size` reports code size per package from the wasm name section (`tools/wasmsize`). `make web-smoke` runs Playwright with the system Chromium on SwiftShader: start-up time, a golden screenshot of `bench` 64, the same picture after a context loss, every scene example for 2 s without faults, and the 1000-sprite bench written to `.cache/web-bench.json`. |
|
| D291 | Playground runtime. One page holds the code editor, a Game View (`canvas[data-aego]`) and Console/Problems. The catalog `app/assembly/playground` is core, input, render2d, scripting, physics and debug (physics joins the playground graph, superseding D205 on that point; `render3d` stays out). Console examples (docs snippets, the donut) run headless as before. Scene examples come from `app/playground/examples`, a regular project (module `game`) embedded in the binary and checked by `aego check` in `make check-all`: its `project.aego` gives the project sections once at start, and each `scenes/<name>.aescene` with `scripts/<name>/` is one example. Run compiles the edited package in the same workspace, links a fresh script runtime and loads the scene through `sceneio` with a loader over an empty index, so only builtin assets resolve. One engine per page is built on the first Run on `platform/web` and `gpu/webgl2` and stays on `requestAnimationFrame`; Run and Stop swap the scene, the runtime and the module attachment of the main world. The page polls `poll` every 250 ms for log lines, faults and frame statistics (frame time, draw calls, WebGL calls of the last frame). The game owns the keyboard and pads while the canvas has focus, Escape included; a click elsewhere releases it. `bench` draws N sprites straight through the renderer for measurements and the screenshot golden, `loseContext` drives `WEBGL_lose_context`. Budgets are targets: `aego.wasm` ≤ 8 MB gzip (the site build fails above it), cold start ≤ 2 s on a laptop. `make playground-size` reports code size per package from the wasm name section (`tools/wasmsize`). `make web-smoke` runs Playwright with the system Chromium on SwiftShader: start-up time, a golden screenshot of `bench` 64, the same picture after a context loss, every scene example for 2 s without faults, and the 1000-sprite bench written to `.cache/web-bench.json`. |
|
||||||
| D292 | Documentation IA. One registry feeds the site, the editor's docs panel and `aego docs export` (version 2, with groups). Sections and groups: Getting started; Guides (Editor, Aego Script, Scenes & Prefabs, Gameplay, Physics 2D, Rendering, Extending Aego, Tools & Agents); Reference (Script API, Components, Project settings, Diagnostics, CLI, Project). A root Markdown page declares `group:` (a guide group) and `order:`, children inherit the group of their root. The kernel lists only the module-independent groups; a module adds its own guide group from its root page with `group_title:` and `group_order:` (Physics 2D comes from `modules/physics`, so `engine/docs` never names a module and needs no literal waiver under D212). Reference groups are filled from the registry: script modules are roots of Script API ordered by the owning module's `DocsOrder`; components hang under one page per module, created only for modules with components and titled `module.Info.DisplayName` (the core module is "Core"); diagnostic codes under Diagnostics; documented project scripts under Project. Entry IDs are unchanged, so `ref:` links keep working. `aego docs check` fails on a root page without a guide group or an order and on a built-in module without a DisplayName. `module.DocSource.Dir`, taken from the module's package path (`Unwrap` sees through catalog wrappers), gives every page its repository path for "Edit this page"; `aego docs build` points those links at the checked-out branch (`master` when git is unavailable). gendocs renders sections with collapsible groups whose open state the browser remembers, the current page highlighted with its group and ancestors open, breadcrumbs (section, group, ancestors), an on-this-page list from the h2/h3 anchors, previous/next in navigation order, search grouped by section, a footer with the engine version, the commit and links to the source, the playground and the page's file, a landing page with three entry points and `docs/hero.png` when present, and `llms.txt`. A text outline of the navigation is the golden of the gendocs tests, for a fixture and for the real export. The playground and the site share one set of colour tokens. Amended in 9.5b3b: component pages come from the schema: a header with category, module, space, scene uniqueness, required components and the Create path; a field table with type, the default read from the registered default, range and unit, notes (editor or script visibility, read-only, Play only, saved, live, choice and bitset sources, enum values, gizmo) and the field doc; component methods (`manifest.CompMethods`) and signals as members (a method that shares its name with a field gets the ID and title `Name()`); the script events of the owning module, listed under Script API → Script events with owners from `module.Manifest.EventOwners`; related guides (pages linking to the component or its fields, and root guides of the same module); an empty Try it slot. Project settings has one page per project section with its fields and default block (`Section.DefaultValue`, `Section.DefaultBlock`; a block that encodes to nothing is left out, and the section page lists the owning module's guides as related) and a Player settings page built from the settings registry. CLI is the authored page `cli.md` in group `ref-cli`, the only reference group a page may declare; a test keeps it listing every command of `cmd/aego`. A script module `aego/<id>` belongs to module `<id>` when no owner is registered. The editor's docs panel shows the same sections and groups. Amended 2026-10-20 (9.7): entries and groups carry an editor icon name (`Entry.Icon`, `Group.Icon`, `module.Info.DocsIcon`, `group_icon:`), and gendocs builds one SVG sprite from the editor icon sources and fails on an unknown name; the site uses the editor theme, and code is highlighted at build time with the editor tokenizer and palette, identifiers linked to their entries. Short names resolve everywhere: `Registry.Aliases` maps a last segment that names one entry (`Body2D`, `ApplyForce`, `timer.Timer`) to it, used by `Registry.Link` in the docs panel and exported for gendocs. Getting started is the install page; the editor tour moved to the Editor group with its IDs. `module.Info.DocsGroup` documents a module's components in a guide group instead of under Components (the Script component is in Aego Script). Script API module pages list their functions with signatures above the sections, with a filter; a function without its own example links to the module's runnable example. A ```aes check block is compiled but not run: component pages end with "From a script" examples compiled against the full builtin catalog (`playground.NewWith`) by the playground tests. |
|
| D292 | Documentation IA. One registry feeds the site, the editor's docs panel and `aego docs export` (version 2, with groups). Sections and groups: Getting started; Guides (Editor, Aego Script, Scenes & Prefabs, Gameplay, Physics 2D, Rendering, Extending Aego, Tools & Agents); Reference (Script API, Components, Project settings, Diagnostics, CLI, Project). A root Markdown page declares `group:` (a guide group) and `order:`, children inherit the group of their root. The kernel lists only the module-independent groups; a module adds its own guide group from its root page with `group_title:` and `group_order:` (Physics 2D comes from `modules/physics`, so `engine/docs` never names a module and needs no literal waiver under D212). Reference groups are filled from the registry: script modules are roots of Script API ordered by the owning module's `DocsOrder`; components hang under one page per module, created only for modules with components and titled `module.Info.DisplayName` (the core module is "Core"); diagnostic codes under Diagnostics; documented project scripts under Project. Entry IDs are unchanged, so `ref:` links keep working. `aego docs check` fails on a root page without a guide group or an order and on a built-in module without a DisplayName. `module.DocSource.Dir`, taken from the module's package path (`Unwrap` sees through catalog wrappers), gives every page its repository path for "Edit this page"; `aego docs build` points those links at the checked-out branch (`master` when git is unavailable). gendocs renders sections with collapsible groups whose open state the browser remembers, the current page highlighted with its group and ancestors open, breadcrumbs (section, group, ancestors), an on-this-page list from the h2/h3 anchors, previous/next in navigation order, search grouped by section, a footer with the engine version, the commit and links to the source, the playground and the page's file, a landing page with three entry points and `docs/hero.png` when present, and `llms.txt`. A text outline of the navigation is the golden of the gendocs tests, for a fixture and for the real export. The playground and the site share one set of colour tokens. Amended in 9.5b3b: component pages come from the schema: a header with category, module, space, scene uniqueness, required components and the Create path; a field table with type, the default read from the registered default, range and unit, notes (editor or script visibility, read-only, Play only, saved, live, choice and bitset sources, enum values, gizmo) and the field doc; component methods (`manifest.CompMethods`) and signals as members (a method that shares its name with a field gets the ID and title `Name()`); the script events of the owning module, listed under Script API → Script events with owners from `module.Manifest.EventOwners`; related guides (pages linking to the component or its fields, and root guides of the same module); an empty Try it slot. Project settings has one page per project section with its fields and default block (`Section.DefaultValue`, `Section.DefaultBlock`; a block that encodes to nothing is left out, and the section page lists the owning module's guides as related) and a Player settings page built from the settings registry. CLI is the authored page `cli.md` in group `ref-cli`, the only reference group a page may declare; a test keeps it listing every command of `cmd/aego`. A script module `aego/<id>` belongs to module `<id>` when no owner is registered. The editor's docs panel shows the same sections and groups. |
|
||||||
| D293 | Docs session. `editor/core/docsession.Session` serves the real Inspector and Game View without the editor shell: one scene from bytes, its own `document.Manager` holding the edited document (the Inspector's `services.Documents`), a second manager with a document over the same scene for `play.Controller`, which swaps in its play proxy during Play, so the Inspector always edits authored values; a `selection.Service` keyed by that document; a minimal `context.Editor` (engine, renderer, loader, types, commands, problems, an in-memory clipboard) and `Unavailable` for asset and scene operations. History works as in the editor; a committed change saves the scene to text (`OnChange`) and restarts Play, keeping a pause; Reset reloads the original bytes. `app/docsview` hosts it: the sandbox catalog, `LoopScope` editor, one canvas with a toolbar (Play/Pause, Reset, Undo, Redo), Game View and Inspector side by side (stacked below 640 px), a script host that links a sandboxed runtime and attaches the modules to the play world, and a JSON bridge (`load`, `reset`, `set`, `undo`, `redo`, `state`, `log`). `cmd/aego-try` builds it as `try/aego-docs.wasm` (`js && aego_editor`). The graph excludes SDL, gl33, the shell, every panel but the two, and the script importer; `asset/pipeline` stays because `editor/core/context`, `assetops` and the preset importer import it, and the Inspector gets no pipeline. Module editor extensions (custom inspectors) are not loaded. |
|
| D293 | Docs session. `editor/core/docsession.Session` serves the real Inspector and Game View without the editor shell: one scene from bytes, its own `document.Manager` holding the edited document (the Inspector's `services.Documents`), a second manager with a document over the same scene for `play.Controller`, which swaps in its play proxy during Play, so the Inspector always edits authored values; a `selection.Service` keyed by that document; a minimal `context.Editor` (engine, renderer, loader, types, commands, problems, an in-memory clipboard) and `Unavailable` for asset and scene operations. History works as in the editor; a committed change saves the scene to text (`OnChange`) and restarts Play, keeping a pause; Reset reloads the original bytes. `app/docsview` hosts it: the sandbox catalog, `LoopScope` editor, one canvas with a toolbar (Play/Pause, Reset, Undo, Redo), Game View and Inspector side by side (stacked below 640 px), a script host that links a sandboxed runtime and attaches the modules to the play world, and a JSON bridge (`load`, `reset`, `set`, `undo`, `redo`, `state`, `log`). `cmd/aego-try` builds it as `try/aego-docs.wasm` (`js && aego_editor`). The graph excludes SDL, gl33, the shell, every panel but the two, and the script importer; `asset/pipeline` stays because `editor/core/context`, `assetops` and the preset importer import it, and the Inspector gets no pipeline. Module editor extensions (custom inspectors) are not loaded. |
|
||||||
| D294 | Try it and the sandbox. A module's `res/docs/try/<Name>.aescene` becomes the try scene of the component whose ID ends in `.<Name>` (preferring the module's own), a guide page can name one with `try:` and `try_select:`; the registry carries `Try` and `TrySelect` and the site renders a Try it block when the playground is built: the scene text, Copy scene, Reset, and a button that lazily loads the one docs runtime per site, moves its single canvas into the block and loads the scene there, selecting the documented component; Inspector edits rewrite the shown text. `aego docs check` runs every try scene on the sandbox catalog for 60 headless frames and fails on a load error, a warning, a fault or a scene from a module outside the sandbox. Sandbox (playground and docs runtime): the catalog is the allowlist of host modules (core, input, render2d, scripting, physics, debug and, since 9.7, tween and render3d with the built-in meshes, whose OBJ importer is left out of web builds; no save, settings, spawn or audio), user files are in memory or localStorage only, there is no file or network access; scripts run with 2M instructions per call and a 64 MB VM heap (`vm.Options.MaxHeap`, checked in the allocator while the machine runs and reported as `CodeBudget`, garbage included until the next collection); a scene above 10 000 nodes stops the game with an error. `aego-docs.wasm` is held to 10 MB gzip by `make site` (`make try-size` reports it per package). Try scenes with scripts and the playground's Inspect toggle come later. Amended 2026-10-20 (9.7): a console run (`aes run`, the playground Run) attaches the sandbox modules to its world as Play does and steps it with `TickWorld` on a windowless engine, so tweens, timers, actions, handles in `yield` and physics behave as in Play; before, scripts were stepped by hand and module calls failed with AES6010. |
|
| D294 | Try it and the sandbox. A module's `res/docs/try/<Name>.aescene` becomes the try scene of the component whose ID ends in `.<Name>` (preferring the module's own), a guide page can name one with `try:` and `try_select:`; the registry carries `Try` and `TrySelect` and the site renders a Try it block when the playground is built: the scene text, Copy scene, Reset, and a button that lazily loads the one docs runtime per site, moves its single canvas into the block and loads the scene there, selecting the documented component; Inspector edits rewrite the shown text. `aego docs check` runs every try scene on the sandbox catalog for 60 headless frames and fails on a load error, a warning, a fault or a scene from a module outside the sandbox. Sandbox (playground and docs runtime): the catalog is the allowlist of host modules (core, input, render2d, scripting, physics, debug; no save, settings, spawn or audio), user files are in memory or localStorage only, there is no file or network access; scripts run with 2M instructions per call and a 64 MB VM heap (`vm.Options.MaxHeap`, checked in the allocator while the machine runs and reported as `CodeBudget`, garbage included until the next collection); a scene above 10 000 nodes stops the game with an error. `aego-docs.wasm` is held to 10 MB gzip by `make site` (`make try-size` reports it per package). Try scenes with scripts and the playground's Inspect toggle come later. |
|
||||||
| D295 | Web export. `aego export --target web` (`js/wasm`, shown as `web`) builds `cmd/aego-runtime` with `GOOS=js GOARCH=wasm CGO_ENABLED=0` and the runtime tag, release with `-trimpath -s -w`, and packs `index.html`, `loader.js` (both embedded in `app/export/web`), the toolchain's `wasm_exec.js` with its license, `game.wasm` and `data/` with the 9.5a manifest and artifacts plus `files.json`. The page script loads the data, not Go: it fetches `files.json`, then the wasm, the manifest and the artifacts six at a time with one progress bar over the uncompressed sizes, and hands the bytes to the runtime in `globalThis.aegoData`; `runtime/player.Main` under js mounts them with `vfs/memfs` after checking every manifest entry and size, as `exportfs` does on desktop. Under js the player has no log or crash files and no signal handling (`hostFiles`); the profile and saves go through `platform.UserFiles()` (localStorage). URL parameters replace the command-line flags; `globalThis.aegoGame` exposes the frame count and profile file access for tests. Files that compress by more than 10% get `.gz` (and `.br` when `brotli` exists); `export.aetext` records `binaryGzip`. Web-specific checks: EXP010 (asset kind the web runtime cannot load, none yet), EXP011 (native module must build without cgo). `make site` exports the platformer to `site/demo/`, the landing page links it (`aego docs build --demo`), and fails when its `game.wasm` is over 8 MB gzip; `make web-smoke` runs it for 120 frames against a screenshot golden and checks that a profile file survives a reload. `make test-wasm` builds the web runtime and rejects native, pipeline and editor packages in its graph. |
|
| D295 | Web export. `aego export --target web` (`js/wasm`, shown as `web`) builds `cmd/aego-runtime` with `GOOS=js GOARCH=wasm CGO_ENABLED=0` and the runtime tag, release with `-trimpath -s -w`, and packs `index.html`, `loader.js` (both embedded in `app/export/web`), the toolchain's `wasm_exec.js` with its license, `game.wasm` and `data/` with the 9.5a manifest and artifacts plus `files.json`. The page script loads the data, not Go: it fetches `files.json`, then the wasm, the manifest and the artifacts six at a time with one progress bar over the uncompressed sizes, and hands the bytes to the runtime in `globalThis.aegoData`; `runtime/player.Main` under js mounts them with `vfs/memfs` after checking every manifest entry and size, as `exportfs` does on desktop. Under js the player has no log or crash files and no signal handling (`hostFiles`); the profile and saves go through `platform.UserFiles()` (localStorage). URL parameters replace the command-line flags; `globalThis.aegoGame` exposes the frame count and profile file access for tests. Files that compress by more than 10% get `.gz` (and `.br` when `brotli` exists); `export.aetext` records `binaryGzip`. Web-specific checks: EXP010 (asset kind the web runtime cannot load, none yet), EXP011 (native module must build without cgo). `make site` exports the platformer to `site/demo/`, the landing page links it (`aego docs build --demo`), and fails when its `game.wasm` is over 8 MB gzip; `make web-smoke` runs it for 120 frames against a screenshot golden and checks that a profile file survives a reload. `make test-wasm` builds the web runtime and rejects native, pipeline and editor packages in its graph. |
|
||||||
|
|
||||||
### 3.21 Stage 9.6 — Agent tooling
|
### 3.21 Stage 9.6 — Agent tooling
|
||||||
@@ -519,12 +519,6 @@ The Stage 8 specification numbered these decisions D153, D155, D158, D159, D168
|
|||||||
| D298 | Project refactorings without the editor. `project/refs` finds what references an asset from the pipeline index (`Entry.Deps`) and `mainScene`, locates the places in a file from its text (node path, owner, field, list index), lists missing assets (with the graveyard entry and a same-name suggestion of the same kind) and node references to nodes a scene does not define; it reads through `refs.Source`, which `*pipeline.Pipeline` satisfies. An ID counts as known when it is a builtin, has a file, or is in the index without a grave, so the page artifact of a multi-file script package is not reported as missing. `project/refactor` plans and applies: `PlanReplace` collects the referring files, `Edits` computes their new bytes by replacing the `asset("<id>")` token (overrides, data assets, tables and presets included, which is why it is not a `mutate.Tx`), `Write` backs the originals up under `.aego/recovery/refs-<time>` and writes atomically, `ApplyProject` moves `mainScene`, `Reimport` reimports what changed; `Users` lists referrers outside a deleted set (safe delete), `Delete` deletes through the pipeline, whose scan buries the IDs in the graveyard; `Move` moves an asset with its `.meta` and rewrites the `package` line of a script that changes package; `PlanRename`, `RenameOps` (with the temporary-name pass when names swap) and `ApplyOps` (rolled back on failure) carry batch rename. The rename rules live in `project/rename`. `project.WriteAtomic` is the one atomic writer (`project.Save` and `document.WriteAtomic` use it; files are written 0644). The editor's References, Missing References, Batch Rename and asset move are adapters: open documents are still edited through their history, files on disk go through `project/refactor`. Prefab Apply and Create From Node follow in 9.6c2. Amended in 9.6c2: prefab Apply and Create From Node work without the editor. `refactor.PlanApply` locates the instance (`Locate`), splits its overrides with `prefab.Pick`, bakes the selection into the prefab models (the baker moved from the editor; `Nearest` routes an apply to the nearest nested prefab) and returns the new models, the instance block before and after; `ApplyPlan.Edits` encodes the changed prefab files. `ApplyPrefab` sets the baked models into the document's library and replaces the instance root and every other prefab instance of the document through `mutate`, so the other instances pick up the new values and keep their overrides; `Result.Inverse` carries the blocks taken before the apply and is valid against the old prefab files. `WriteAll` writes the files and rolls back on failure. `mutate.Document.Bytes` saves through `Env.Library`, so overrides on instances are written. `CheckCreate`, `LinkBlock` and `CreatePrefab` turn a node into a prefab file (written, scanned, imported) and link the node to it keeping every node ID; a failure removes the file and its `.meta`. `PrefabLibrary` builds a library from the prefab sources, so tools need no loader. The editor's Apply and Create Prefab call the same functions and keep their history across documents. The data asset and table editors (`editor/core/dataedit`) edit a `mutate.DataDocument`: every change is a `data-set`, `data-reset`, `data-base` (new: the base asset, by ID or path), `row-add`, `row-remove`, `row-restore` or `row-rename` with exact inverses, consecutive edits of one field merge, a value equal to the inherited one (the base for a data asset, the type default for a table row) resets the field, and saving writes the document's own bytes, so untouched fields keep their text. Gizmo property drags go through `commands.SetField` after `props.Normalize` clamps them to the field range; `commands.SetProperty` is gone. |
|
| D298 | Project refactorings without the editor. `project/refs` finds what references an asset from the pipeline index (`Entry.Deps`) and `mainScene`, locates the places in a file from its text (node path, owner, field, list index), lists missing assets (with the graveyard entry and a same-name suggestion of the same kind) and node references to nodes a scene does not define; it reads through `refs.Source`, which `*pipeline.Pipeline` satisfies. An ID counts as known when it is a builtin, has a file, or is in the index without a grave, so the page artifact of a multi-file script package is not reported as missing. `project/refactor` plans and applies: `PlanReplace` collects the referring files, `Edits` computes their new bytes by replacing the `asset("<id>")` token (overrides, data assets, tables and presets included, which is why it is not a `mutate.Tx`), `Write` backs the originals up under `.aego/recovery/refs-<time>` and writes atomically, `ApplyProject` moves `mainScene`, `Reimport` reimports what changed; `Users` lists referrers outside a deleted set (safe delete), `Delete` deletes through the pipeline, whose scan buries the IDs in the graveyard; `Move` moves an asset with its `.meta` and rewrites the `package` line of a script that changes package; `PlanRename`, `RenameOps` (with the temporary-name pass when names swap) and `ApplyOps` (rolled back on failure) carry batch rename. The rename rules live in `project/rename`. `project.WriteAtomic` is the one atomic writer (`project.Save` and `document.WriteAtomic` use it; files are written 0644). The editor's References, Missing References, Batch Rename and asset move are adapters: open documents are still edited through their history, files on disk go through `project/refactor`. Prefab Apply and Create From Node follow in 9.6c2. Amended in 9.6c2: prefab Apply and Create From Node work without the editor. `refactor.PlanApply` locates the instance (`Locate`), splits its overrides with `prefab.Pick`, bakes the selection into the prefab models (the baker moved from the editor; `Nearest` routes an apply to the nearest nested prefab) and returns the new models, the instance block before and after; `ApplyPlan.Edits` encodes the changed prefab files. `ApplyPrefab` sets the baked models into the document's library and replaces the instance root and every other prefab instance of the document through `mutate`, so the other instances pick up the new values and keep their overrides; `Result.Inverse` carries the blocks taken before the apply and is valid against the old prefab files. `WriteAll` writes the files and rolls back on failure. `mutate.Document.Bytes` saves through `Env.Library`, so overrides on instances are written. `CheckCreate`, `LinkBlock` and `CreatePrefab` turn a node into a prefab file (written, scanned, imported) and link the node to it keeping every node ID; a failure removes the file and its `.meta`. `PrefabLibrary` builds a library from the prefab sources, so tools need no loader. The editor's Apply and Create Prefab call the same functions and keep their history across documents. The data asset and table editors (`editor/core/dataedit`) edit a `mutate.DataDocument`: every change is a `data-set`, `data-reset`, `data-base` (new: the base asset, by ID or path), `row-add`, `row-remove`, `row-restore` or `row-rename` with exact inverses, consecutive edits of one field merge, a value equal to the inherited one (the base for a data asset, the type default for a table row) resets the field, and saving writes the document's own bytes, so untouched fields keep their text. Gizmo property drags go through `commands.SetField` after `props.Normalize` clamps them to the field range; `commands.SetProperty` is gone. |
|
||||||
| D299 | Agent layer and CLI. `app/agent` is one catalog of commands (`Command`: name `group.verb`, typed arguments with positional order, write flag, `Run(*Session, Args) (*Output, error)`) that the CLI and the MCP server share; a command's MCP tool name and JSON schema come from the same definition. A `Session` loads types, modules, importers and the pipeline like `app.Build` without the engine, renderer or loader, publishes the compiled script types, builds the prefab library from sources (`refactor.PrefabLibrary`) and resolves scenes and prefabs from files; it edits through `scene/mutate` and `project/refactor` and writes atomically, then reimports and reloads the library. Commands: `scene tree|get|set|reset|add-node|remove-node|move|rename|add-comp|remove-comp|apply|validate|new|run`, `prefab create|instantiate|apply|revert|unpack|overrides`, `data get|set|reset|base|rows|add-row|remove-row|rename-row|import|export`, `refs find|missing|fix`, `asset list|move|rename|delete`, `schema list|show`, `project validate`. The CLI form is `aego <group> <verb> <positional> [--flags]`; flags may follow positionals, `--` ends them, a value starting with one dash is positional. `--json` prints `Output` (ok, dryRun, changes, problems, created, files, notes, value) or `{"ok":false,"error":…}`; exit 0, 1 on error, 2 on error-level problems or, with `--strict`, any problem. `--dry-run` applies and rolls back in memory. A project with native code runs the commands in its own binary (`--tool agent`), so its types are known. When another process holds the asset index (`Pipeline.Shared`), commands that create assets refuse, so an asset never gets a derived ID the editor would later replace. `scene run` releases the session's pipeline and runs a scene headless in process through `run.AgentRunner` (no window, no player profile, warnings and errors of the log, the world hash of `run.WorldHash`). Module `OnAdd` hooks run without the editor's visual bounds. `prefab-instantiate` creates an owner instance block (as the editor does), not the runtime spawn copy. Closes D242. |
|
| D299 | Agent layer and CLI. `app/agent` is one catalog of commands (`Command`: name `group.verb`, typed arguments with positional order, write flag, `Run(*Session, Args) (*Output, error)`) that the CLI and the MCP server share; a command's MCP tool name and JSON schema come from the same definition. A `Session` loads types, modules, importers and the pipeline like `app.Build` without the engine, renderer or loader, publishes the compiled script types, builds the prefab library from sources (`refactor.PrefabLibrary`) and resolves scenes and prefabs from files; it edits through `scene/mutate` and `project/refactor` and writes atomically, then reimports and reloads the library. Commands: `scene tree|get|set|reset|add-node|remove-node|move|rename|add-comp|remove-comp|apply|validate|new|run`, `prefab create|instantiate|apply|revert|unpack|overrides`, `data get|set|reset|base|rows|add-row|remove-row|rename-row|import|export`, `refs find|missing|fix`, `asset list|move|rename|delete`, `schema list|show`, `project validate`. The CLI form is `aego <group> <verb> <positional> [--flags]`; flags may follow positionals, `--` ends them, a value starting with one dash is positional. `--json` prints `Output` (ok, dryRun, changes, problems, created, files, notes, value) or `{"ok":false,"error":…}`; exit 0, 1 on error, 2 on error-level problems or, with `--strict`, any problem. `--dry-run` applies and rolls back in memory. A project with native code runs the commands in its own binary (`--tool agent`), so its types are known. When another process holds the asset index (`Pipeline.Shared`), commands that create assets refuse, so an asset never gets a derived ID the editor would later replace. `scene run` releases the session's pipeline and runs a scene headless in process through `run.AgentRunner` (no window, no player profile, warnings and errors of the log, the world hash of `run.WorldHash`). Module `OnAdd` hooks run without the editor's visual bounds. `prefab-instantiate` creates an owner instance block (as the editor does), not the runtime spawn copy. Closes D242. |
|
||||||
| D300 | MCP server. `aego mcp` (`app/agent/mcp`) speaks MCP over stdio, one JSON-RPC message per line, without a dependency: `initialize` (protocol 2025-06-18, older 2025-03-26 and 2024-11-05 echoed), `ping`, `tools/list|call`, `resources/list|read|subscribe|unsubscribe`, `resources/templates/list`, `notifications/resources/updated|list_changed`. Tools are the D299 commands one to one (name with underscores, the command's JSON schema, `readOnlyHint` for commands that do not write) plus `docs_search` and `docs_read`; a failing command returns `isError` with the message, a successful one its `Output` as text and `structuredContent`. Resources: `aego://project/manifest`, `aego://project/problems`, `aego://docs/index|<id>`, `aego://schema/index|<type>`, `aego://file/<path>` for scenes, prefabs, data assets and tables. The server keeps an `agent.Kit` (types, modules, importers) and opens a pipeline session per request (a few milliseconds on the platformer), so it never holds the asset index lock between calls and an editor started later owns it. `--watch` polls the project with the pipeline's watcher and notifies subscribed resources; a document that appears or disappears also sends `list_changed`. A native project is served by its own binary (`--tool mcp`, stdin passed through). |
|
| D300 | MCP server. `aego mcp` (`app/agent/mcp`) speaks MCP over stdio, one JSON-RPC message per line, without a dependency: `initialize` (protocol 2025-06-18, older 2025-03-26 and 2024-11-05 echoed), `ping`, `tools/list|call`, `resources/list|read|subscribe|unsubscribe`, `resources/templates/list`, `notifications/resources/updated|list_changed`. Tools are the D299 commands one to one (name with underscores, the command's JSON schema, `readOnlyHint` for commands that do not write) plus `docs_search` and `docs_read`; a failing command returns `isError` with the message, a successful one its `Output` as text and `structuredContent`. Resources: `aego://project/manifest`, `aego://project/problems`, `aego://docs/index|<id>`, `aego://schema/index|<type>`, `aego://file/<path>` for scenes, prefabs, data assets and tables. The server keeps an `agent.Kit` (types, modules, importers) and opens a pipeline session per request (a few milliseconds on the platformer), so it never holds the asset index lock between calls and an editor started later owns it. `--watch` polls the project with the pipeline's watcher and notifies subscribed resources; a document that appears or disappears also sends `list_changed`. A native project is served by its own binary (`--tool mcp`, stdin passed through). |
|
||||||
| D301 | Release builds. The `release` job of `ci.yml` runs after `checks` and `build` on every push: master publishes the rolling pre-release `nightly` (the release and its tag are recreated at the new commit), a `v*` tag publishes that release. `tools/release/release.sh [--dry-run] <vX.Y.Z|nightly>` builds `cmd/aego` with cgo, `-trimpath -s -w` for the runner's GOOS/GOARCH (Linux amd64 only for now), packs `aego-<version|nightly>-<os>-<arch>.tar.gz` (`bin/aego`, `VERSION` with tag and commit, `README.txt`), lists every file in one `SHA256SUMS.txt` (the Gitea instance accepts only some attachment types: `.gz`, `.zip`, `.txt`, not `.exe` or `.sha256`), and uploads them through the Gitea API with the job token; a tag that differs from `engine.Version` fails. `tools/release/install.sh`, served at the site root, picks the latest release (`--nightly`, `--version vX.Y.Z`), verifies the checksum, unpacks to `<prefix>/share/aego` and links `<prefix>/bin/aego` (`~/.local` by default), installs missing runtime libraries (Mesa GL and EGL, X11, Wayland, xkbcommon) with the system package manager unless `--no-deps`, and warns about glibc older than 2.39 or OpenGL below 3.3; other systems are told to build from source. On the same Linux runner the job cross-builds Windows with mingw-w64 (`tools/bootstrap-native.sh` sets `CMAKE_SYSTEM_NAME=Windows` when it is not on Windows): `aego.exe`, `aego-editor.exe` (GUI subsystem) and the NSIS installer, shipped zipped as `aego-<version>-windows-amd64-setup.zip` (`tools/release/windows/aego.nsi`: per user under `%LOCALAPPDATA%\Programs\Aego`, no administrator rights, Start menu and desktop shortcuts, the directory on the user `PATH`, an entry in Settings → Apps; running a newer installer updates in place), plus a `.zip`; `install.ps1` at the site root downloads the latest installer archive, checks it against `SHA256SUMS.txt`, unpacks it and runs the installer silently. A prebuilt `aego` edits, runs and exports script-only projects without Go; native modules and exports to other systems need Go and a checkout (`AEGO_ROOT` or `--engine`). |
|
|
||||||
| D302 | Documentation screenshots. `make shots` opens a copy of `examples/platformer` in the real editor under a clean user profile with `AEGO_SHOTS=<dir>` (`app/edit/shots.go`: a fixed sequence of editor states, each after a fixed number of frames) and writes `docs/shots/*.png`, which are committed; `make shots-check` (part of `make ci`) regenerates them and `tools/shotdiff` fails when more than 3% of the pixels of any image moved by more than 24/255. Frames are read with `Engine.CaptureFrame`, a one-shot hook between `EndFrame` and the swap that reads the default framebuffer through the optional `gpu.BackbufferReader` (implemented by GL 3.3 only). Pages use them as ``; gendocs copies the directory to `assets/shots/`, the editor's docs panel skips images, and `docs/shots/demo.png` is the poster of the landing page demo. |
|
|
||||||
| D303 | Docs delivery. The landing page embeds the web demo as a click-to-play iframe over its poster and starts it at once for a visitor who has played it before (a localStorage mark). The Try it loader compiles `aego-docs.wasm` straight from the network response, so the browser keeps both the file and its compiled code in the HTTP cache, and counts a copy of the first download for a progress bar against the uncompressed size written into the page; a localStorage mark of the versioned URL tells later pages the runtime was started before, and they start the first Try it block that scrolls into view without waiting for a click (the Cache API was tried and dropped: responses rebuilt from it lose the compiled-code cache, and probing the HTTP cache with `only-if-cached` holds its lock on the same URL); the block shows the scene as a tree (`sceneio.Outline`, node → components → typed fields linked to their docs, values updated from the runtime and changed ones marked) with Open in editor (downloads the `.aescene`) and Copy as .aescene. The docs runtime shows the `physics.shapes` and `physics.joints` debug draw channels, so colliders and joints are visible without art. nginx serves every `.wasm` with a brotli or gzip copy made at image build, `?v=` URLs as `immutable` for a year, HTML and JSON for 60 seconds (`no-cache` kept the edge proxy in front of the site from storing new pages, so it served stale ones), and `install.sh` as text; the web export preloads its `game.wasm`. Amended 2026-10-20: the site sits behind the edge proxy at 185.170.196.188, whose route to mainserv is slow (~0.2 MB/s) and drops, so the edge serves aego from its own copy (`deploy/edge`): the image carries `__edge/site.tar.gz` and `__edge/version`, the edge timer `aego-wasm-sync` fetches a new site archive with resumable downloads and switches `/var/www/aego-site/current` atomically, and copies each wasm into `/var/www/aego-wasm/<version>/`; anything not there yet goes to mainserv through the proxy, so a new `?v=` never gets old bytes. Pages show the commit in the header, and styles, scripts and the icon sprite are loaded as `?v=<hash of their contents>`, so a new revision reaches every browser within the 60 s HTML lifetime. |
|
|
||||||
| D304 | Camera adjustment is a pull contract. `contracts/camera2d` declares `Adjuster` under `camera2d.adjust`. `render2d.cameras` builds the view of every active Camera 2D from its interpolated pose, then passes it through `view = Adjust(node, view)` when the play world has the service; the value form keeps the per-frame path free of allocations. Rendering never names the camera module and the camera module never names render systems (D204); the contract may import `render/view` (a depcheck exception). An output is evaluated once per `camera.update` tick; a second Camera 2D on the same output reuses the pose. The edit world has no adjuster, fixed views are never adjusted. The module ID `camera` collides with the scene view state field `"camera"`, which is excepted in depcheck. |
|
|
||||||
| D305 | Virtual cameras, priorities, blends and zones. Each output picks the enabled virtual camera with the highest priority; ties go to the lower node handle index, then zones over plain cameras, the later zone over the earlier, and BlendTo/Cut over everything until Release. Zones are derived each tick from the exact world position of the target (not the interpolated one), so entering is deterministic; they never write component fields. A switch blends from the current logical pose, zoom in log space; a replaced blend completes with finished=false. Blends are awaitable handles `aego/camera.Blend`. Outputs are chosen per Camera 2D by `camera.CameraBrain2D`; without it the camera shows the main output. |
|
|
||||||
| D306 | Follow, shake, accessibility and time. Follow reads the interpolated target pose and runs in game time with exponential, rate-independent damping, a deadzone in view fractions, lookahead in seconds of velocity and snapping on teleports. Shake is trauma^exponent times deterministic gradient noise seeded by the output name; it never touches node transforms or the world RNG. Accessibility provides `camera2d.accessibility` with its setting IDs: Screen Shake scales shake, Reduce Motion disables shake and lookahead. Blends and shake follow the VC time domain (unscaled by default). The rewind hash covers active cameras, overrides, zones and game-domain blends and trauma; follow is captured but not hashed, unscaled blends finish on restore. |
|
|
||||||
|
|
||||||
## 4. Dependency rules
|
## 4. Dependency rules
|
||||||
|
|
||||||
@@ -967,7 +961,6 @@ Packages are named by capability (`render/view`, `asset/loader`, `scene/sceneio`
|
|||||||
| 9.5b | Web runtime: `platform/web`, `gpu/webgl2`, ES shaders and `render/shaderx`, `platform.Driver`, `platform.UserFS`, playground with Game View, docs IA |
|
| 9.5b | Web runtime: `platform/web`, `gpu/webgl2`, ES shaders and `render/shaderx`, `platform.Driver`, `platform.UserFS`, playground with Game View, docs IA |
|
||||||
| 9.5c | Web export, docs session with Inspector and Game View, Try it, playground sandbox |
|
| 9.5c | Web export, docs session with Inspector and Game View, Try it, playground sandbox |
|
||||||
| 9.6 | Agent tooling: `scene/mutate` (transactions with exact inverses and redo, data documents), `project/refs`, `project/refactor`, `project/rename`, editor structure edits through mutate, `app/agent` command catalog and `aego <group> <verb>` CLI, `aego mcp`, external edits in the editor |
|
| 9.6 | Agent tooling: `scene/mutate` (transactions with exact inverses and redo, data documents), `project/refs`, `project/refactor`, `project/rename`, editor structure edits through mutate, `app/agent` command catalog and `aego <group> <verb>` CLI, `aego mcp`, external edits in the editor |
|
||||||
| 9.7 | Docs and release: editor theme and icons on the site, build-time highlighting with links, short-name references, Try it progress, cache and scene tree, Try scenes for every 2D physics component, script examples on component pages, Script API index and module examples, diagnostics with checked examples, install page, release builds and install script, editor screenshots; done |
|
|
||||||
| 13+ | Export/pak, networking/store, Android |
|
| 13+ | Export/pak, networking/store, Android |
|
||||||
|
|
||||||
### Stage 9 complete
|
### Stage 9 complete
|
||||||
@@ -1048,12 +1041,6 @@ Packages are named by capability (`render/view`, `asset/loader`, `scene/sceneio`
|
|||||||
| D298 | Project refactorings without the editor | 9.6c1 |
|
| D298 | Project refactorings without the editor | 9.6c1 |
|
||||||
| D299 | Agent layer and CLI | 9.6d1 |
|
| D299 | Agent layer and CLI | 9.6d1 |
|
||||||
| D300 | MCP server | 9.6d2 |
|
| D300 | MCP server | 9.6d2 |
|
||||||
| D301 | Release builds | 9.7 |
|
|
||||||
| D302 | Documentation screenshots | 9.7 |
|
|
||||||
| D303 | Docs delivery | 9.7 |
|
|
||||||
| D304 | Camera adjustment contract | 10a |
|
|
||||||
| D305 | Virtual cameras, blends and zones | 10a |
|
|
||||||
| D306 | Follow, shake, accessibility and time | 10a |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ BENCH_PHYSICS_PKGS :=
|
|||||||
.DEFAULT_GOAL := bootstrap
|
.DEFAULT_GOAL := bootstrap
|
||||||
-include modules/*/module.mk
|
-include modules/*/module.mk
|
||||||
|
|
||||||
.PHONY: bootstrap build test test-nonative test-editor test-runtime test-gl vet lint depcheck smoke run genfont genicons icons icons-doc res genpreviews golden golden-update site-headless bench bench-baseline bench-scripts bench-build bench-compare bench-profile bench-scripts-run bench-allocs playground playground-size site site-check cli example example-platformer check-all ci clean fuzz runtime export-smoke editor-app shaders test-wasm web-smoke web-smoke-update try-size shots shots-check
|
.PHONY: bootstrap build test test-nonative test-editor test-runtime test-gl vet lint depcheck smoke run genfont genicons icons icons-doc res genpreviews golden golden-update site-headless bench bench-baseline bench-scripts bench-build bench-compare bench-profile bench-scripts-run bench-allocs playground playground-size site site-check cli example example-platformer check-all ci clean fuzz runtime export-smoke editor-app shaders test-wasm web-smoke web-smoke-update try-size
|
||||||
|
|
||||||
XVFB := $(shell command -v xvfb-run 2>/dev/null)
|
XVFB := $(shell command -v xvfb-run 2>/dev/null)
|
||||||
GL_OVERRIDE = MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330
|
GL_OVERRIDE = MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330
|
||||||
@@ -212,7 +212,7 @@ site: cli
|
|||||||
@$(TRY_SIZE_CHECK)
|
@$(TRY_SIZE_CHECK)
|
||||||
|
|
||||||
playground:
|
playground:
|
||||||
@if GOOS=js GOARCH=wasm go list -deps ./cmd/aego-playground | grep -E '^aego/(platform/sdl|gpu/gl33|asset/pipeline|asset/importers)'; then echo "playground graph pulls a native or pipeline package"; exit 1; fi
|
@if GOOS=js GOARCH=wasm go list -deps ./cmd/aego-playground | grep -E '^aego/(platform/sdl|gpu/gl33|asset/pipeline|asset/importers|modules/render3d)'; then echo "playground graph pulls a native or pipeline package"; exit 1; fi
|
||||||
$(MAKE) site
|
$(MAKE) site
|
||||||
|
|
||||||
site-headless:
|
site-headless:
|
||||||
@@ -282,29 +282,7 @@ fuzz:
|
|||||||
go test -run '^$$' -fuzz '^FuzzDecodeVerify$$' -fuzztime $(FUZZTIME) ./script/verify
|
go test -run '^$$' -fuzz '^FuzzDecodeVerify$$' -fuzztime $(FUZZTIME) ./script/verify
|
||||||
go test -run '^$$' -fuzz '^FuzzDecodeVerifyV3$$' -fuzztime $(FUZZTIME) ./script/verify
|
go test -run '^$$' -fuzz '^FuzzDecodeVerifyV3$$' -fuzztime $(FUZZTIME) ./script/verify
|
||||||
|
|
||||||
ci: build vet lint depcheck test test-nonative test-editor test-runtime test-wasm runtime smoke fuzz golden shots-check test-gl cli playground web-smoke site-check example example-platformer check-all export-smoke $(MODULE_CI) bench-allocs
|
ci: build vet lint depcheck test test-nonative test-editor test-runtime test-wasm runtime smoke fuzz golden test-gl cli playground web-smoke site-check example example-platformer check-all export-smoke $(MODULE_CI) bench-allocs
|
||||||
|
|
||||||
|
|
||||||
# Documentation screenshots of the real editor on the platformer example, under a clean user profile.
|
|
||||||
SHOTS_TMP = $(CURDIR)/.cache/shots
|
|
||||||
ifneq ($(XVFB),)
|
|
||||||
SHOTS_ENV = $(XVFB) -a -s "-screen 0 1600x900x24" env SDL_VIDEODRIVER=x11 LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe $(GL_OVERRIDE)
|
|
||||||
else
|
|
||||||
SHOTS_ENV = $(GL_ENV)
|
|
||||||
endif
|
|
||||||
SHOTS_RUN = rm -rf $(SHOTS_TMP)/project $(SHOTS_TMP)/home && mkdir -p $(SHOTS_TMP)/home && cp -R examples/platformer $(SHOTS_TMP)/project && \
|
|
||||||
rm -rf $(SHOTS_TMP)/project/.aego $(SHOTS_TMP)/project/exports && \
|
|
||||||
sed -i.bak 's|=> ../..|=> $(CURDIR)|' $(SHOTS_TMP)/project/go.mod && \
|
|
||||||
XDG_CONFIG_HOME=$(SHOTS_TMP)/home XDG_DATA_HOME=$(SHOTS_TMP)/home $(SHOTS_ENV) env AEGO_SHOTS=$(1) .cache/bin/aego edit $(SHOTS_TMP)/project --hidden
|
|
||||||
|
|
||||||
shots: cli
|
|
||||||
rm -rf docs/shots
|
|
||||||
$(call SHOTS_RUN,$(CURDIR)/docs/shots)
|
|
||||||
|
|
||||||
shots-check: cli
|
|
||||||
rm -rf $(SHOTS_TMP)/out
|
|
||||||
$(call SHOTS_RUN,$(SHOTS_TMP)/out)
|
|
||||||
go run ./tools/shotdiff -want docs/shots -got $(SHOTS_TMP)/out
|
|
||||||
|
|
||||||
runtime: bootstrap
|
runtime: bootstrap
|
||||||
go build -tags aego_runtime -o .cache/bin/aego-runtime ./cmd/aego-runtime
|
go build -tags aego_runtime -o .cache/bin/aego-runtime ./cmd/aego-runtime
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"aego/engine/module"
|
"aego/engine/module"
|
||||||
"aego/modules/accessibility"
|
"aego/modules/accessibility"
|
||||||
"aego/modules/audio"
|
"aego/modules/audio"
|
||||||
"aego/modules/camera"
|
|
||||||
"aego/modules/dataasset"
|
"aego/modules/dataasset"
|
||||||
"aego/modules/debug"
|
"aego/modules/debug"
|
||||||
"aego/modules/display"
|
"aego/modules/display"
|
||||||
@@ -72,10 +71,6 @@ func tweenEntry() assembly.Entry {
|
|||||||
return assembly.Entry{ID: tween.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return tween.New() }}
|
return assembly.Entry{ID: tween.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return tween.New() }}
|
||||||
}
|
}
|
||||||
|
|
||||||
func cameraEntry() assembly.Entry {
|
|
||||||
return assembly.Entry{ID: camera.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return camera.New() }}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dataEntry() assembly.Entry {
|
func dataEntry() assembly.Entry {
|
||||||
return assembly.Entry{ID: dataasset.ID, New: func(assembly.Options) module.Module { return dataasset.New() }}
|
return assembly.Entry{ID: dataasset.ID, New: func(assembly.Options) module.Module { return dataasset.New() }}
|
||||||
}
|
}
|
||||||
@@ -90,5 +85,5 @@ func savegameEntry() assembly.Entry {
|
|||||||
|
|
||||||
func Default() assembly.Catalog {
|
func Default() assembly.Catalog {
|
||||||
return assembly.Catalog{core(), dataEntry(), inputEntry(), render2dEntry(), render3dEntry(), scriptEntry(), physicsEntry(), debugEntry(),
|
return assembly.Catalog{core(), dataEntry(), inputEntry(), render2dEntry(), render3dEntry(), scriptEntry(), physicsEntry(), debugEntry(),
|
||||||
displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), cameraEntry(), spawnEntry(), savegameEntry()}
|
displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), spawnEntry(), savegameEntry()}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ package builtin
|
|||||||
import "aego/app/assembly"
|
import "aego/app/assembly"
|
||||||
|
|
||||||
func Headless() assembly.Catalog {
|
func Headless() assembly.Catalog {
|
||||||
return assembly.Catalog{core(), dataEntry(), inputEntry(), scriptEntry(), physicsEntry(), debugEntry(), displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), cameraEntry(), spawnEntry(), savegameEntry()}
|
return assembly.Catalog{core(), dataEntry(), inputEntry(), scriptEntry(), physicsEntry(), debugEntry(), displayEntry(), audioEntry(), accessibilityEntry(), settingsEntry(), tweenEntry(), spawnEntry(), savegameEntry()}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package editors
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"aego/app/assembly"
|
"aego/app/assembly"
|
||||||
"aego/modules/camera"
|
|
||||||
cameraeditor "aego/modules/camera/editor"
|
|
||||||
"aego/modules/debug"
|
"aego/modules/debug"
|
||||||
debugeditor "aego/modules/debug/editor"
|
debugeditor "aego/modules/debug/editor"
|
||||||
inputmod "aego/modules/input"
|
inputmod "aego/modules/input"
|
||||||
@@ -22,6 +20,5 @@ func With(c assembly.Catalog) assembly.Catalog {
|
|||||||
With(render2d.ID, render2deditor.Hook).
|
With(render2d.ID, render2deditor.Hook).
|
||||||
With(render3d.ID, render3deditor.Hook).
|
With(render3d.ID, render3deditor.Hook).
|
||||||
With(physics.ID, physicseditor.Hook).
|
With(physics.ID, physicseditor.Hook).
|
||||||
With(camera.ID, cameraeditor.Hook).
|
|
||||||
With(debug.ID, debugeditor.Hook)
|
With(debug.ID, debugeditor.Hook)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,11 @@ import (
|
|||||||
"aego/app/assembly"
|
"aego/app/assembly"
|
||||||
"aego/engine/base"
|
"aego/engine/base"
|
||||||
"aego/engine/module"
|
"aego/engine/module"
|
||||||
"aego/modules/camera"
|
|
||||||
"aego/modules/debug"
|
"aego/modules/debug"
|
||||||
inputmod "aego/modules/input"
|
inputmod "aego/modules/input"
|
||||||
"aego/modules/physics"
|
"aego/modules/physics"
|
||||||
"aego/modules/render2d"
|
"aego/modules/render2d"
|
||||||
"aego/modules/render3d"
|
|
||||||
"aego/modules/scripting"
|
"aego/modules/scripting"
|
||||||
"aego/modules/tween"
|
|
||||||
"aego/script/stdlib"
|
"aego/script/stdlib"
|
||||||
"aego/script/vm"
|
"aego/script/vm"
|
||||||
)
|
)
|
||||||
@@ -26,9 +23,6 @@ func Catalog() assembly.Catalog {
|
|||||||
}},
|
}},
|
||||||
{ID: physics.ID, New: func(assembly.Options) module.Module { return physics.New() }},
|
{ID: physics.ID, New: func(assembly.Options) module.Module { return physics.New() }},
|
||||||
{ID: debug.ID, New: func(assembly.Options) module.Module { return debug.New() }},
|
{ID: debug.ID, New: func(assembly.Options) module.Module { return debug.New() }},
|
||||||
{ID: tween.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return tween.New() }},
|
|
||||||
{ID: camera.ID, Requires: []string{scripting.ID}, New: func(assembly.Options) module.Module { return camera.New() }},
|
|
||||||
{ID: render3d.ID, New: func(assembly.Options) module.Module { return render3d.New() }},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package docstool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"aego/app/playground"
|
|
||||||
"aego/engine/docs"
|
|
||||||
"aego/script/diag"
|
|
||||||
)
|
|
||||||
|
|
||||||
func diagnoser(pg *playground.Playground) func(string, *docs.DiagInfo) {
|
|
||||||
return func(code string, d *docs.DiagInfo) {
|
|
||||||
got, err := pg.Reproduce(code)
|
|
||||||
if err != nil || got.Code == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.Severity, d.At, d.Message = got.Severity, got.File, got.Message
|
|
||||||
d.Line, d.Col, d.EndLine, d.EndCol = got.Line, got.Col, got.EndLine, got.EndCol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiagnosticProblems lists codes whose example is missing or no longer produces the code.
|
|
||||||
func DiagnosticProblems() []docs.Problem {
|
|
||||||
pg, err := playground.New()
|
|
||||||
if err != nil {
|
|
||||||
return []docs.Problem{{ID: docs.DiagnosticsID, Error: true, Message: "cannot start the docs runtime: " + err.Error()}}
|
|
||||||
}
|
|
||||||
var out []docs.Problem
|
|
||||||
for _, c := range diag.Codes() {
|
|
||||||
if _, err := pg.Reproduce(c); err != nil {
|
|
||||||
out = append(out, docs.Problem{ID: c, Error: true, Message: err.Error()})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
@@ -138,7 +138,7 @@ func TestF1Refs(t *testing.T) {
|
|||||||
if err := os.WriteFile(path, []byte(probe), 0o644); err != nil {
|
if err := os.WriteFile(path, []byte(probe), 0o644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
reg, ws, err := build(p, nil, nil)
|
reg, ws, err := build(p, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -169,17 +169,3 @@ func TestF1Refs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiagnosticExamplesExported(t *testing.T) {
|
|
||||||
if ps := DiagnosticProblems(); len(ps) > 0 {
|
|
||||||
t.Fatalf("diagnostics examples: %v", ps)
|
|
||||||
}
|
|
||||||
reg, err := ExportRegistry(nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
e, ok := reg.Get("AES2001")
|
|
||||||
if !ok || e.Diag == nil || e.Diag.Line == 0 || e.Diag.Severity != "error" || !strings.Contains(e.Diag.Message, "heigth") {
|
|
||||||
t.Fatalf("AES2001 example was not diagnosed: %+v", e.Diag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"aego/app/assembly"
|
"aego/app/assembly"
|
||||||
"aego/app/assembly/builtin"
|
"aego/app/assembly/builtin"
|
||||||
"aego/app/playground"
|
|
||||||
"aego/engine/docs"
|
"aego/engine/docs"
|
||||||
"aego/engine/module"
|
"aego/engine/module"
|
||||||
scriptsystems "aego/modules/scripting/systems"
|
scriptsystems "aego/modules/scripting/systems"
|
||||||
@@ -21,7 +20,7 @@ import (
|
|||||||
"aego/script/workspace"
|
"aego/script/workspace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func build(p *project.Project, mods []module.Module, diagnose func(string, *docs.DiagInfo)) (*docs.Registry, *workspace.Workspace, error) {
|
func build(p *project.Project, mods []module.Module) (*docs.Registry, *workspace.Workspace, error) {
|
||||||
types, err := assembly.NewTypes()
|
types, err := assembly.NewTypes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -42,7 +41,6 @@ func build(p *project.Project, mods []module.Module, diagnose func(string, *docs
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
src := docs.SourcesOf(bm, types.Schema(), man)
|
src := docs.SourcesOf(bm, types.Schema(), man)
|
||||||
src.Diagnose = diagnose
|
|
||||||
var ws *workspace.Workspace
|
var ws *workspace.Workspace
|
||||||
if p != nil {
|
if p != nil {
|
||||||
ws, err = workspace.Open(workspace.Config{Root: p.Root, Module: p.Module, Scripts: p.ScriptsDir(), Bindings: reg.Table(), Docs: bm})
|
ws, err = workspace.Open(workspace.Config{Root: p.Root, Module: p.Module, Scripts: p.ScriptsDir(), Bindings: reg.Table(), Docs: bm})
|
||||||
@@ -63,17 +61,7 @@ func build(p *project.Project, mods []module.Module, diagnose func(string, *docs
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Registry(p *project.Project, mods []module.Module) (*docs.Registry, error) {
|
func Registry(p *project.Project, mods []module.Module) (*docs.Registry, error) {
|
||||||
r, _, err := build(p, mods, nil)
|
r, _, err := build(p, mods)
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExportRegistry is Registry with the diagnostics examples compiled for their messages and positions.
|
|
||||||
func ExportRegistry(p *project.Project, mods []module.Module) (*docs.Registry, error) {
|
|
||||||
pg, err := playground.New()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r, _, err := build(p, mods, diagnoser(pg))
|
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,9 +84,9 @@ func WriteExport(reg *docs.Registry, title, path string, w io.Writer) error {
|
|||||||
return os.WriteFile(path, data, 0o644)
|
return os.WriteFile(path, data, 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Check(reg *docs.Registry, strict bool, w io.Writer, extra ...docs.Problem) int {
|
func Check(reg *docs.Registry, strict bool, w io.Writer) int {
|
||||||
errs, warns := 0, 0
|
errs, warns := 0, 0
|
||||||
for _, p := range append(reg.Check(strict), extra...) {
|
for _, p := range reg.Check(strict) {
|
||||||
level := "warn "
|
level := "warn "
|
||||||
if p.Error {
|
if p.Error {
|
||||||
level = "error"
|
level = "error"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"aego/app/assembly/builtin"
|
|
||||||
"aego/engine"
|
"aego/engine"
|
||||||
"aego/engine/module"
|
"aego/engine/module"
|
||||||
"aego/project"
|
"aego/project"
|
||||||
@@ -41,7 +40,7 @@ func (e *Env) export(path string) error {
|
|||||||
}
|
}
|
||||||
return e.Native(args...)
|
return e.Native(args...)
|
||||||
}
|
}
|
||||||
reg, err := ExportRegistry(e.Project, e.Modules)
|
reg, err := Registry(e.Project, e.Modules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -72,7 +71,7 @@ func (e *Env) Check(strict bool) int {
|
|||||||
fmt.Fprintln(e.Stderr, "aego:", err)
|
fmt.Fprintln(e.Stderr, "aego:", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
code := Check(reg, strict, e.Stdout, DiagnosticProblems()...)
|
code := Check(reg, strict, e.Stdout)
|
||||||
if CheckTry(reg, e.Stdout) > 0 && code == 0 {
|
if CheckTry(reg, e.Stdout) > 0 && code == 0 {
|
||||||
code = 1
|
code = 1
|
||||||
}
|
}
|
||||||
@@ -99,10 +98,7 @@ func (e *Env) git(args ...string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Env) commit() string {
|
func (e *Env) commit() string {
|
||||||
if c := e.git("rev-parse", "--short", "HEAD"); c != "" {
|
return e.git("rev-parse", "--short", "HEAD")
|
||||||
return c
|
|
||||||
}
|
|
||||||
return os.Getenv("AEGO_COMMIT")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Env) branch() string {
|
func (e *Env) branch() string {
|
||||||
@@ -154,14 +150,6 @@ func (e *Env) Build(out string, play bool) error {
|
|||||||
if c := e.commit(); c != "" {
|
if c := e.commit(); c != "" {
|
||||||
args = append(args, "-commit", c)
|
args = append(args, "-commit", c)
|
||||||
}
|
}
|
||||||
var icons []string
|
|
||||||
for _, d := range builtin.IconSources() {
|
|
||||||
icons = append(icons, filepath.Join(e.Engine, filepath.FromSlash(d)))
|
|
||||||
}
|
|
||||||
args = append(args, "-icons", strings.Join(icons, ","))
|
|
||||||
if shots := filepath.Join(e.Engine, "docs", "shots"); fileExists(filepath.Join(shots, "demo.png")) {
|
|
||||||
args = append(args, "-poster", filepath.Join(shots, "demo.png"), "-shots", shots)
|
|
||||||
}
|
|
||||||
if hero := filepath.Join(e.Engine, "docs", "hero.png"); fileExists(hero) {
|
if hero := filepath.Join(e.Engine, "docs", "hero.png"); fileExists(hero) {
|
||||||
args = append(args, "-hero", hero)
|
args = append(args, "-hero", hero)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,7 @@ func Tool(p *project.Project, args []string, stdout, stderr io.Writer, mods ...m
|
|||||||
if err := fs.Parse(args[1:]); err != nil {
|
if err := fs.Parse(args[1:]); err != nil {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
build := Registry
|
reg, err := Registry(p, mods)
|
||||||
if args[0] == "export" {
|
|
||||||
build = ExportRegistry
|
|
||||||
}
|
|
||||||
reg, err := build(p, mods)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(stderr, "aego:", err)
|
fmt.Fprintln(stderr, "aego:", err)
|
||||||
return 1
|
return 1
|
||||||
@@ -38,7 +34,7 @@ func Tool(p *project.Project, args []string, stdout, stderr io.Writer, mods ...m
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
case "check":
|
case "check":
|
||||||
return Check(reg, *strict, stdout, DiagnosticProblems()...)
|
return Check(reg, *strict, stdout)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(stderr, "unknown docs command %q\n", args[0])
|
fmt.Fprintf(stderr, "unknown docs command %q\n", args[0])
|
||||||
return 2
|
return 2
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
playmods "aego/app/assembly/playground"
|
playmods "aego/app/assembly/playground"
|
||||||
"aego/asset"
|
"aego/asset"
|
||||||
"aego/asset/loader"
|
"aego/asset/loader"
|
||||||
"aego/contracts/debugdraw"
|
|
||||||
"aego/core/log"
|
"aego/core/log"
|
||||||
"aego/core/mathx"
|
"aego/core/mathx"
|
||||||
edclip "aego/editor/core/clipboard"
|
edclip "aego/editor/core/clipboard"
|
||||||
@@ -21,7 +20,6 @@ import (
|
|||||||
"aego/engine/base"
|
"aego/engine/base"
|
||||||
"aego/engine/module"
|
"aego/engine/module"
|
||||||
"aego/format/aetext"
|
"aego/format/aetext"
|
||||||
"aego/modules/physics"
|
|
||||||
scriptsystems "aego/modules/scripting/systems"
|
scriptsystems "aego/modules/scripting/systems"
|
||||||
"aego/platform"
|
"aego/platform"
|
||||||
"aego/render"
|
"aego/render"
|
||||||
@@ -43,8 +41,6 @@ const (
|
|||||||
maxSprites = 16384
|
maxSprites = 16384
|
||||||
gameShare = 0.6
|
gameShare = 0.6
|
||||||
stackBelow = 640
|
stackBelow = 640
|
||||||
// inspectorMin keeps field names and values readable in narrow embeds.
|
|
||||||
inspectorMin = 400
|
|
||||||
maxLines = 200
|
maxLines = 200
|
||||||
defaultW = 960
|
defaultW = 960
|
||||||
defaultH = 440
|
defaultH = 440
|
||||||
@@ -81,7 +77,6 @@ type App struct {
|
|||||||
log *log.Logger
|
log *log.Logger
|
||||||
lines []Line
|
lines []Line
|
||||||
textOn bool
|
textOn bool
|
||||||
shown *engine.World
|
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,7 +270,7 @@ func layout(body mathx.Rect, gap float32) (game, insp mathx.Rect) {
|
|||||||
return mathx.Rect{X: body.X, Y: body.Y, W: body.W, H: h},
|
return mathx.Rect{X: body.X, Y: body.Y, W: body.W, H: h},
|
||||||
mathx.Rect{X: body.X, Y: body.Y + h + gap, W: body.W, H: max(0, body.H-h-gap)}
|
mathx.Rect{X: body.X, Y: body.Y + h + gap, W: body.W, H: max(0, body.H-h-gap)}
|
||||||
}
|
}
|
||||||
w := min(body.W*gameShare, body.W-inspectorMin)
|
w := body.W * gameShare
|
||||||
return mathx.Rect{X: body.X, Y: body.Y, W: w, H: body.H},
|
return mathx.Rect{X: body.X, Y: body.Y, W: w, H: body.H},
|
||||||
mathx.Rect{X: body.X + w + gap, Y: body.Y, W: max(0, body.W-w-gap), H: body.H}
|
mathx.Rect{X: body.X + w + gap, Y: body.Y, W: max(0, body.W-w-gap), H: body.H}
|
||||||
}
|
}
|
||||||
@@ -313,24 +308,10 @@ func (a *App) toolbar(r mathx.Rect) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// showPhysics draws collider and joint outlines in the game view, since docs scenes have no art for them.
|
|
||||||
func (a *App) showPhysics() {
|
|
||||||
w := a.session.Controller().World()
|
|
||||||
if w == nil || w == a.shown {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if d, ok := engine.ServiceOf[debugdraw.Draw](w, debugdraw.ServiceID); ok {
|
|
||||||
d.Hide(physics.ChannelShapes, false)
|
|
||||||
d.Hide(physics.ChannelJoints, false)
|
|
||||||
a.shown = w
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) Update(dt float64) {
|
func (a *App) Update(dt float64) {
|
||||||
e := a.e
|
e := a.e
|
||||||
now := e.Platform().Now()
|
now := e.Platform().Now()
|
||||||
a.session.Tick(dt)
|
a.session.Tick(dt)
|
||||||
a.showPhysics()
|
|
||||||
ui := a.ui
|
ui := a.ui
|
||||||
ui.Time = now
|
ui.Time = now
|
||||||
m := e.Metrics()
|
m := e.Metrics()
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func tries(t *testing.T) []docs.Entry {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
out := p.TryScenes()
|
out := p.TryScenes()
|
||||||
if len(out) < 10 {
|
if len(out) < 4 {
|
||||||
t.Fatalf("only %d try scenes; does the module embed res/docs/try?", len(out))
|
t.Fatalf("only %d try scenes; does the module embed res/docs/try?", len(out))
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"aego/editor/core/docsession"
|
"aego/editor/core/docsession"
|
||||||
"aego/scene/sceneio"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type request struct {
|
type request struct {
|
||||||
@@ -20,7 +19,6 @@ type request struct {
|
|||||||
|
|
||||||
type Reply struct {
|
type Reply struct {
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitempty"`
|
||||||
Values map[string]string `json:"values,omitempty"`
|
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
State *docsession.State `json:"state,omitempty"`
|
State *docsession.State `json:"state,omitempty"`
|
||||||
Log []Line `json:"log,omitempty"`
|
Log []Line `json:"log,omitempty"`
|
||||||
@@ -63,16 +61,7 @@ func (a *App) Call(method string, payload []byte) Reply {
|
|||||||
return Reply{Error: err.Error()}
|
return Reply{Error: err.Error()}
|
||||||
}
|
}
|
||||||
st := s.State()
|
st := s.State()
|
||||||
return Reply{Text: string(s.Text()), Values: Values(s.Text()), State: &st}
|
return Reply{Text: string(s.Text()), State: &st}
|
||||||
}
|
|
||||||
|
|
||||||
// Values flattens a scene file into the field keys the docs scene tree uses.
|
|
||||||
func Values(text []byte) map[string]string {
|
|
||||||
nodes, err := sceneio.Outline(text)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return sceneio.OutlineValues(nodes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Reply(method, payload string) string {
|
func (a *App) Reply(method, payload string) string {
|
||||||
|
|||||||
@@ -154,9 +154,6 @@ func Run(p *project.Project, cfg Config, mods ...sdk.Module) error {
|
|||||||
panic(rec)
|
panic(rec)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if dir := os.Getenv(EnvShots); dir != "" {
|
|
||||||
return s.shots(dir)
|
|
||||||
}
|
|
||||||
if err := s.Run(); err != nil {
|
if err := s.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ canvas { display: block; width: 100%; height: 100%; outline: none; touch-action:
|
|||||||
#fullscreen { position: fixed; top: 10px; right: 10px; width: 36px; height: 36px; border: 0; border-radius: 6px; background: rgba(0, 0, 0, 0.45); color: #fff; font-size: 18px; cursor: pointer; opacity: 0.5; }
|
#fullscreen { position: fixed; top: 10px; right: 10px; width: 36px; height: 36px; border: 0; border-radius: 6px; background: rgba(0, 0, 0, 0.45); color: #fff; font-size: 18px; cursor: pointer; opacity: 0.5; }
|
||||||
#fullscreen:hover { opacity: 1; }
|
#fullscreen:hover { opacity: 1; }
|
||||||
</style>
|
</style>
|
||||||
<link rel="preload" href="game.wasm?v={{.Build}}" as="fetch" type="application/wasm" crossorigin>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="game" data-aego tabindex="0" aria-label="{{.Title}}"></canvas>
|
<canvas id="game" data-aego tabindex="0" aria-label="{{.Title}}"></canvas>
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
package playground
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"aego/app/assembly/builtin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCheckSnippetsCompile(t *testing.T) {
|
|
||||||
p, err := NewWith(builtin.Default())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
n := 0
|
|
||||||
for _, e := range p.docs.All() {
|
|
||||||
for _, s := range e.Snippets {
|
|
||||||
if !s.Check || s.Lang != "aes" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
for _, d := range p.Compile(map[string]string{"main.aes": s.Code}) {
|
|
||||||
if d.Severity == "error" {
|
|
||||||
t.Errorf("%s (line %d of its page): %d:%d %s %s", e.ID, s.Line, d.Line, d.Col, d.Code, d.Message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
t.Fatal("no aes check blocks in the documentation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package playground
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"aego/script/diag"
|
|
||||||
)
|
|
||||||
|
|
||||||
const exampleFrames = 3
|
|
||||||
|
|
||||||
// Reproduce runs the diagnostics example of code and returns what its broken version reports.
|
|
||||||
func (p *Playground) Reproduce(code string) (Diagnostic, error) {
|
|
||||||
ex, ok := diag.Example(code)
|
|
||||||
if !ok {
|
|
||||||
return Diagnostic{}, errors.New("no example in script/diag/examples.go")
|
|
||||||
}
|
|
||||||
if ex.Why != "" {
|
|
||||||
return Diagnostic{}, nil
|
|
||||||
}
|
|
||||||
if ex.Bad == "" || ex.Good == "" {
|
|
||||||
return Diagnostic{}, errors.New("the example needs both a broken and a fixed version")
|
|
||||||
}
|
|
||||||
bad := reports(p.Run(ex.Sources(false), exampleFrames))
|
|
||||||
var found *Diagnostic
|
|
||||||
for i := range bad {
|
|
||||||
if bad[i].Code == code {
|
|
||||||
found = &bad[i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if found == nil {
|
|
||||||
return Diagnostic{}, fmt.Errorf("the broken example does not report %s, it reports %s", code, list(bad))
|
|
||||||
}
|
|
||||||
var wrong []Diagnostic
|
|
||||||
for _, d := range reports(p.Run(ex.Sources(true), exampleFrames)) {
|
|
||||||
if d.Code == code || d.Severity == "error" {
|
|
||||||
wrong = append(wrong, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(wrong) > 0 {
|
|
||||||
return *found, fmt.Errorf("the fixed example still reports %s", list(wrong))
|
|
||||||
}
|
|
||||||
return *found, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func reports(res Result) []Diagnostic {
|
|
||||||
out := append([]Diagnostic(nil), res.Diagnostics...)
|
|
||||||
for _, f := range res.Faults {
|
|
||||||
out = append(out, Diagnostic{File: f.File, Line: f.Line, Code: f.Code, Severity: "error", Message: f.Message})
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func list(ds []Diagnostic) string {
|
|
||||||
if len(ds) == 0 {
|
|
||||||
return "nothing"
|
|
||||||
}
|
|
||||||
parts := make([]string, len(ds))
|
|
||||||
for i, d := range ds {
|
|
||||||
parts[i] = strings.TrimSpace(d.Code + " " + d.Message)
|
|
||||||
}
|
|
||||||
return strings.Join(parts, "; ")
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package playground
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"aego/script/diag"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDiagnosticExamples(t *testing.T) {
|
|
||||||
p := open(t)
|
|
||||||
var skipped []string
|
|
||||||
for _, code := range diag.Codes() {
|
|
||||||
d, err := p.Reproduce(code)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: %v", code, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if d.Code == "" {
|
|
||||||
skipped = append(skipped, code)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.Logf("%s %s:%d:%d-%d:%d %s %s", code, d.File, d.Line, d.Col, d.EndLine, d.EndCol, d.Severity, d.Message)
|
|
||||||
}
|
|
||||||
if len(skipped) > 10 {
|
|
||||||
t.Errorf("%d codes are not reproducible: %v", len(skipped), skipped)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -101,16 +101,9 @@ type Playground struct {
|
|||||||
proj *exampleProject
|
proj *exampleProject
|
||||||
gameCfg GameConfig
|
gameCfg GameConfig
|
||||||
game *Game
|
game *Game
|
||||||
|
|
||||||
simEngine *engine.Engine
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() (*Playground, error) {
|
func New() (*Playground, error) {
|
||||||
return NewWith(playmods.Catalog())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWith builds a playground over another module catalog, for checks that need every module's types.
|
|
||||||
func NewWith(cat assembly.Catalog) (*Playground, error) {
|
|
||||||
proj, err := loadProject()
|
proj, err := loadProject()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -120,7 +113,7 @@ func NewWith(cat assembly.Catalog) (*Playground, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reg := bind.New()
|
reg := bind.New()
|
||||||
man, err := cat.Load(types, reg, assembly.Options{Seed: 1, Testing: true})
|
man, err := playmods.Catalog().Load(types, reg, assembly.Options{Seed: 1, Testing: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -212,23 +205,6 @@ func diagnostics(sm *source.Manager, ds []diag.Diagnostic) []Diagnostic {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func blocked(failed map[string][]diag.Diagnostic) []Diagnostic {
|
|
||||||
pkgs := make([]string, 0, len(failed))
|
|
||||||
for pkg := range failed {
|
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
}
|
|
||||||
sort.Strings(pkgs)
|
|
||||||
var out []Diagnostic
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
for _, d := range failed[pkg] {
|
|
||||||
if d.Code == diag.CompileBlocked {
|
|
||||||
out = append(out, Diagnostic{Code: d.Code, Message: d.Message, Severity: severity(d)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func failed(err error) []Diagnostic {
|
func failed(err error) []Diagnostic {
|
||||||
return []Diagnostic{{Severity: "error", Message: err.Error()}}
|
return []Diagnostic{{Severity: "error", Message: err.Error()}}
|
||||||
}
|
}
|
||||||
@@ -287,7 +263,6 @@ func (p *Playground) Run(files map[string]string, frames int) Result {
|
|||||||
gen, ds := p.ws.Build()
|
gen, ds := p.ws.Build()
|
||||||
res := Result{Diagnostics: diagnostics(gen.Sources, ds)}
|
res := Result{Diagnostics: diagnostics(gen.Sources, ds)}
|
||||||
if len(gen.Failed) > 0 || diag.HasErrors(ds) {
|
if len(gen.Failed) > 0 || diag.HasErrors(ds) {
|
||||||
res.Diagnostics = append(res.Diagnostics, blocked(gen.Failed)...)
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
pkgs := make([]string, 0, len(gen.Modules))
|
pkgs := make([]string, 0, len(gen.Modules))
|
||||||
@@ -320,17 +295,16 @@ func (p *Playground) Run(files map[string]string, frames int) Result {
|
|||||||
}
|
}
|
||||||
w := engine.NewWorld("playground")
|
w := engine.NewWorld("playground")
|
||||||
w.Input = &input.State{}
|
w.Input = &input.State{}
|
||||||
w.SetService(scene.ServiceID, s)
|
ctx := engine.Context{World: w, Dt: 1.0 / 60}
|
||||||
w.SetService(runtime.ServiceID, rt)
|
|
||||||
e, detach, err := p.attach(w, s, lg)
|
|
||||||
if err != nil {
|
|
||||||
res.Diagnostics = append(res.Diagnostics, failed(err)...)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
defer detach()
|
|
||||||
for i := 0; i < frames; i++ {
|
for i := 0; i < frames; i++ {
|
||||||
frame = uint64(i + 1)
|
ctx.Frame++
|
||||||
e.TickWorld(w, engine.ScopePlay, 1.0/60)
|
frame = ctx.Frame
|
||||||
|
rt.RunDeferred()
|
||||||
|
rt.FixedUpdate(&ctx)
|
||||||
|
rt.Update(&ctx)
|
||||||
|
rt.Coroutines(&ctx)
|
||||||
|
s.Flush()
|
||||||
|
s.UpdateTransforms()
|
||||||
}
|
}
|
||||||
res.Frames = frames
|
res.Frames = frames
|
||||||
for _, f := range rt.Faults() {
|
for _, f := range rt.Faults() {
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
package playground
|
|
||||||
|
|
||||||
import (
|
|
||||||
"aego/core/log"
|
|
||||||
"aego/engine"
|
|
||||||
"aego/engine/module"
|
|
||||||
"aego/scene"
|
|
||||||
"aego/scene/frame"
|
|
||||||
)
|
|
||||||
|
|
||||||
// sim is a windowless engine that steps the module systems of a console run, so tweens, timers and physics work there as in Play.
|
|
||||||
func (p *Playground) sim() (*engine.Engine, error) {
|
|
||||||
if p.simEngine != nil {
|
|
||||||
return p.simEngine, nil
|
|
||||||
}
|
|
||||||
b := engine.NewBuilder(engine.Config{Headless: true, HeadlessFrameTime: 1.0 / 60, Seed: 1, AppID: "aego-playground-console"}).WithLogger(log.Nop())
|
|
||||||
if err := frame.Register(b.Registry()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
e, err := b.Use(p.man.Engine()).Build()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := p.man.Init(&module.InitContext{Engine: e, Log: log.Nop(), Sections: p.sections}); err != nil {
|
|
||||||
e.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p.simEngine = e
|
|
||||||
return e, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Playground) attach(w *engine.World, s *scene.Scene, lg *log.Logger) (*engine.Engine, func(), error) {
|
|
||||||
e, err := p.sim()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
w.Time.Configure(e.TimeConfig())
|
|
||||||
detach, err := p.man.Attach(&module.AttachContext{World: w, Scene: s, Log: lg, Play: true})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return e, detach, nil
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
|
||||||
"aego/app/docsview"
|
"aego/app/docsview"
|
||||||
@@ -18,8 +17,7 @@ func main() {
|
|||||||
} else {
|
} else {
|
||||||
app.OnChange = func(text string) {
|
app.OnChange = func(text string) {
|
||||||
if f := g.Get("aegoDocsChanged"); f.Type() == js.TypeFunction {
|
if f := g.Get("aegoDocsChanged"); f.Type() == js.TypeFunction {
|
||||||
values, _ := json.Marshal(docsview.Values([]byte(text)))
|
f.Invoke(text)
|
||||||
f.Invoke(text, string(values))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
api.Set("call", js.FuncOf(func(_ js.Value, args []js.Value) any {
|
api.Set("call", js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
package camera2d
|
|
||||||
|
|
||||||
import (
|
|
||||||
"aego/render/view"
|
|
||||||
"aego/scene"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ServiceID = "camera2d.adjust"
|
|
||||||
AccessibilityKind = "camera2d.accessibility"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Adjuster interface {
|
|
||||||
Adjust(node scene.NodeHandle, v view.View) view.View
|
|
||||||
}
|
|
||||||
|
|
||||||
type Accessibility struct {
|
|
||||||
ScreenShake string
|
|
||||||
ReducedMotion string
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,12 @@
|
|||||||
FROM golang:1.27-alpine AS build
|
FROM golang:1.27-alpine AS build
|
||||||
RUN apk add --no-cache make gzip brotli
|
RUN apk add --no-cache make gzip
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
COPY tools/gendocs/go.mod tools/gendocs/go.sum tools/gendocs/
|
COPY tools/gendocs/go.mod tools/gendocs/go.sum tools/gendocs/
|
||||||
RUN go mod download && cd tools/gendocs && go mod download
|
RUN go mod download && cd tools/gendocs && go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
ARG AEGO_COMMIT=""
|
|
||||||
ENV AEGO_COMMIT=$AEGO_COMMIT
|
|
||||||
RUN make site-headless
|
RUN make site-headless
|
||||||
RUN find site -type f \( -name '*.js' -o -name '*.css' -o -name '*.html' -o -name '*.json' -o -name '*.svg' \) -exec gzip -9 -k -f {} \;
|
RUN find site -type f \( -name '*.js' -o -name '*.css' -o -name '*.html' -o -name '*.json' -o -name '*.svg' \) -exec gzip -9 -k -f {} \;
|
||||||
RUN find site -type f -name '*.wasm' | while read -r f; do [ -f "$f.gz" ] || gzip -9 -k -f "$f"; [ -f "$f.br" ] || brotli -q 11 -k -f "$f"; done
|
|
||||||
COPY tools/release/install.sh tools/release/install.ps1 site/
|
|
||||||
# the edge proxy serves the site from its own copy: everything but the wasm binaries, which it fetches separately
|
|
||||||
RUN mkdir -p site/__edge && tar -czf site/__edge/site.tar.gz --exclude='./__edge' --exclude='*.wasm' --exclude='*.wasm.gz' --exclude='*.wasm.br' -C site . \
|
|
||||||
&& sha256sum site/__edge/site.tar.gz | cut -c1-16 > site/__edge/version
|
|
||||||
|
|
||||||
FROM nginx:1.27-alpine
|
FROM nginx:1.27-alpine
|
||||||
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: deploy/Dockerfile
|
dockerfile: deploy/Dockerfile
|
||||||
args:
|
|
||||||
AEGO_COMMIT: ${AEGO_COMMIT:-}
|
|
||||||
image: aego-site:latest
|
image: aego-site:latest
|
||||||
container_name: aego-site
|
container_name: aego-site
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ else
|
|||||||
git checkout -q -f --detach "origin/$ref"
|
git checkout -q -f --detach "origin/$ref"
|
||||||
fi
|
fi
|
||||||
sha=$(git rev-parse HEAD)
|
sha=$(git rev-parse HEAD)
|
||||||
AEGO_COMMIT=$(git rev-parse --short HEAD) docker compose -f deploy/compose.yml build
|
docker compose -f deploy/compose.yml build
|
||||||
docker compose -f deploy/compose.yml up -d --wait --remove-orphans
|
docker compose -f deploy/compose.yml up -d --wait --remove-orphans
|
||||||
docker image prune -f >/dev/null
|
docker image prune -f >/dev/null
|
||||||
echo "$sha" >"$state/deployed"
|
echo "$sha" >"$state/deployed"
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Copies the wasm binaries of the deployed aego site from mainserv to this edge, so the edge serves
|
|
||||||
# them itself (see aego-edge.conf). Runs from a timer; slow downloads happen here, not for visitors.
|
|
||||||
set -eu
|
|
||||||
host=aego.akarpov.ru
|
|
||||||
origin=79.142.92.250
|
|
||||||
dir=/var/www/aego-wasm
|
|
||||||
exec 9>/run/aego-wasm-sync.lock
|
|
||||||
flock -n 9 || exit 0
|
|
||||||
|
|
||||||
get() {
|
|
||||||
curl -fsS -m 900 --retry 20 --retry-delay 2 --retry-all-errors --resolve "$host:443:$origin" "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
# pull resumes a large download across the drops of the route to mainserv
|
|
||||||
pull() {
|
|
||||||
dst=$1
|
|
||||||
shift
|
|
||||||
n=0
|
|
||||||
until curl -fsS -m 900 -C - --resolve "$host:443:$origin" -o "$dst" "$@"; do
|
|
||||||
n=$((n + 1))
|
|
||||||
[ "$n" -lt 30 ] || return 1
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
meta() {
|
|
||||||
sed -n "s/.*name=\"$1\" content=\"\([0-9a-f]*\)\".*/\1/p" | head -n 1
|
|
||||||
}
|
|
||||||
|
|
||||||
site=/var/www/aego-site
|
|
||||||
ver=$(get "https://$host/__edge/version" | tr -cd '0-9a-f')
|
|
||||||
if [ -n "$ver" ] && [ ! -d "$site/$ver" ]; then
|
|
||||||
mkdir -p "$site"
|
|
||||||
rm -rf "$site/$ver.part"
|
|
||||||
mkdir "$site/$ver.part"
|
|
||||||
rm -f "$site/$ver.tar.gz"
|
|
||||||
if pull "$site/$ver.tar.gz" "https://$host/__edge/site.tar.gz" &&
|
|
||||||
tar -xzf "$site/$ver.tar.gz" -C "$site/$ver.part"; then
|
|
||||||
chmod -R a+rX "$site/$ver.part"
|
|
||||||
mv "$site/$ver.part" "$site/$ver"
|
|
||||||
ln -sfn "$ver" "$site/current.new"
|
|
||||||
mv -T "$site/current.new" "$site/current"
|
|
||||||
echo "aego-wasm-sync: site $ver"
|
|
||||||
fi
|
|
||||||
rm -rf "$site/$ver.part" "$site/$ver.tar.gz"
|
|
||||||
fi
|
|
||||||
cur=$(readlink "$site/current" 2>/dev/null || true)
|
|
||||||
find "$site" -mindepth 1 -maxdepth 1 -type d -mtime +2 | while read -r d; do
|
|
||||||
[ "$(basename "$d")" = "$cur" ] || rm -rf "$d"
|
|
||||||
done
|
|
||||||
|
|
||||||
page=$(get "https://$host/index.html")
|
|
||||||
try=$(printf '%s' "$page" | meta aego-try)
|
|
||||||
play=$(printf '%s' "$page" | meta aego-play)
|
|
||||||
demo=$(get "https://$host/demo/" | sed -n 's/.*data-build="\([0-9a-f]*\)".*/\1/p' | head -n 1)
|
|
||||||
|
|
||||||
fetch() {
|
|
||||||
v=$1 path=$2
|
|
||||||
[ -n "$v" ] || return 0
|
|
||||||
for enc in br gzip; do
|
|
||||||
ext=$enc
|
|
||||||
[ "$enc" = gzip ] && ext=gz
|
|
||||||
out="$dir/$v/$path.$ext"
|
|
||||||
[ -s "$out" ] && continue
|
|
||||||
mkdir -p "$(dirname "$out")"
|
|
||||||
tmp="$out.part"
|
|
||||||
hdr="$out.hdr"
|
|
||||||
rm -f "$tmp"
|
|
||||||
if pull "$tmp" -H "Accept-Encoding: $enc" -D "$hdr" "https://$host/$path?v=$v" &&
|
|
||||||
grep -qi "^content-encoding: $enc" "$hdr"; then
|
|
||||||
mv "$tmp" "$out"
|
|
||||||
chmod a+r "$out"
|
|
||||||
echo "aego-wasm-sync: $v/$path.$ext"
|
|
||||||
fi
|
|
||||||
rm -f "$tmp" "$hdr"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch "$try" try/aego-docs.wasm
|
|
||||||
fetch "$play" play/aego.wasm
|
|
||||||
fetch "$demo" demo/game.wasm
|
|
||||||
|
|
||||||
# keep the current versions and anything from the last two weeks
|
|
||||||
find "$dir" -mindepth 1 -maxdepth 1 -type d -mtime +14 | while read -r d; do
|
|
||||||
case "$(basename "$d")" in "$try" | "$play" | "$demo") ;; *) rm -rf "$d" ;; esac
|
|
||||||
done
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Copy aego wasm binaries from mainserv to the edge
|
|
||||||
After=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/local/bin/aego-wasm-sync
|
|
||||||
Nice=10
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=Copy aego wasm binaries to the edge every two minutes
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=1min
|
|
||||||
OnUnitActiveSec=2min
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
@@ -1,18 +1,3 @@
|
|||||||
map $arg_v $aego_cache {
|
|
||||||
"" "public, max-age=300";
|
|
||||||
default "public, max-age=31536000, immutable";
|
|
||||||
}
|
|
||||||
|
|
||||||
map $http_accept_encoding $aego_br {
|
|
||||||
"~*\bbr\b" ".br";
|
|
||||||
default "";
|
|
||||||
}
|
|
||||||
|
|
||||||
map $aego_br $aego_br_encoding {
|
|
||||||
".br" "br";
|
|
||||||
default "";
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
@@ -25,32 +10,8 @@ server {
|
|||||||
return 200 "ok";
|
return 200 "ok";
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ \.wasm$ {
|
|
||||||
types { }
|
|
||||||
default_type application/wasm;
|
|
||||||
add_header Cache-Control $aego_cache;
|
|
||||||
add_header Content-Encoding $aego_br_encoding;
|
|
||||||
add_header Vary Accept-Encoding;
|
|
||||||
try_files $uri$aego_br $uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ ^/install\.(sh|ps1)$ {
|
|
||||||
default_type text/plain;
|
|
||||||
add_header Cache-Control "no-cache";
|
|
||||||
}
|
|
||||||
|
|
||||||
location ~ \.(html|json)$ {
|
|
||||||
add_header Cache-Control "public, max-age=60";
|
|
||||||
try_files $uri =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = / {
|
|
||||||
add_header Cache-Control "public, max-age=60";
|
|
||||||
try_files /index.html =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
add_header Cache-Control $aego_cache;
|
add_header Cache-Control "public, max-age=300";
|
||||||
try_files $uri $uri.html $uri/ =404;
|
try_files $uri $uri.html $uri/ =404;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ The kernel (`schema`, `scene`, `render`, `asset`, `engine`, `script`, `ui`, `pro
|
|||||||
| Frame phases and scheduling | `PhasePhysics`, `PhasePostPhysics`, `SystemDesc.Before/After` inside one module |
|
| Frame phases and scheduling | `PhasePhysics`, `PhasePostPhysics`, `SystemDesc.Before/After` inside one module |
|
||||||
| Scene data | presentation pose (`SetPresentationLocal`), component defaults, `OnAdd` |
|
| Scene data | presentation pose (`SetPresentationLocal`), component defaults, `OnAdd` |
|
||||||
| Script host | host structs, component methods, script events (`module.Registry.ScriptEvent`), `bind.Ctx` |
|
| Script host | host structs, component methods, script events (`module.Registry.ScriptEvent`), `bind.Ctx` |
|
||||||
| Contracts | `contracts/scriptevents`, `contracts/physics2d`, `contracts/camera2d`: interfaces between modules and scripts |
|
| Contracts | `contracts/scriptevents`, `contracts/physics2d`: interfaces between modules and scripts |
|
||||||
| Editor core | documents, history, selection, `assetops`, `fuzzy`, gizmos, vectors, play, `services` (Frame and the panel services) |
|
| Editor core | documents, history, selection, `assetops`, `fuzzy`, gizmos, vectors, play, `services` (Frame and the panel services) |
|
||||||
| Editor extension points | `editor/ext`: `SettingsPage`, `Problems`, `Profiler`, `GameOverlay`, `Vectors`, `Overlay`, `ext/inspect` custom inspectors |
|
| Editor extension points | `editor/ext`: `SettingsPage`, `Problems`, `Profiler`, `GameOverlay`, `Vectors`, `Overlay`, `ext/inspect` custom inspectors |
|
||||||
|
|
||||||
@@ -30,8 +30,6 @@ The kernel (`schema`, `scene`, `render`, `asset`, `engine`, `script`, `ui`, `pro
|
|||||||
| lets the playground reach native or pipeline code | `wasm-reaches-forbidden` | keep it out of the playground profile |
|
| lets the playground reach native or pipeline code | `wasm-reaches-forbidden` | keep it out of the playground profile |
|
||||||
| holds or receives `*context.Editor` in `editor/panels/*` | `tests/modulecheck/panels_test.go` | take the services the panel needs in its `Deps` (D216) |
|
| holds or receives `*context.Editor` in `editor/panels/*` | `tests/modulecheck/panels_test.go` | take the services the panel needs in its `Deps` (D216) |
|
||||||
|
|
||||||
A module can tune another module's behaviour without importing it by providing a value under a contract kind: `r.Provide(camera2d.AccessibilityKind, camera2d.Accessibility{ScreenShake: "...", ReducedMotion: "..."})` hands the camera module the IDs of settings it reads every frame. The consumer reads `ctx.Provided(kind)` in `Init`.
|
|
||||||
|
|
||||||
## Proof with a fake module
|
## Proof with a fake module
|
||||||
|
|
||||||
A rule holds when a test module that is not the real one can use the same extension point. `tests/modulecheck/fakephys` registers a project section, inspector hints, a script event, component hooks, a settings page, a panel, a custom inspector, a gizmo, bounds, overlays, a vector layer and a profiler section without importing `modules/physics`; the guard test there checks that the kernel and the editor do not import or name any module.
|
A rule holds when a test module that is not the real one can use the same extension point. `tests/modulecheck/fakephys` registers a project section, inspector hints, a script event, component hooks, a settings page, a panel, a custom inspector, a gizmo, bounds, overlays, a vector layer and a profiler section without importing `modules/physics`; the guard test there checks that the kernel and the editor do not import or name any module.
|
||||||
|
|||||||
@@ -24,12 +24,11 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
|
|||||||
| `code.hints` | engine | 16 | Scripts → Parameter Name Hints | ok |
|
| `code.hints` | engine | 16 | Scripts → Parameter Name Hints | ok |
|
||||||
| `code.implementation` | engine | 16 | Scripts → Go to Implementation | ok |
|
| `code.implementation` | engine | 16 | Scripts → Go to Implementation | ok |
|
||||||
| `comp.box` | engine | 16 | component physics.BoxCollider2D | ok |
|
| `comp.box` | engine | 16 | component physics.BoxCollider2D | ok |
|
||||||
| `comp.camera2d` | engine | 16 | component camera.CameraBrain2D; component engine.Camera2D; template render2d.camera | ok |
|
| `comp.camera2d` | engine | 16 | component engine.Camera2D; template render2d.camera | ok |
|
||||||
| `comp.camera3d` | engine | 16 | component engine.Camera3D; template render3d.camera | ok |
|
| `comp.camera3d` | engine | 16 | component engine.Camera3D; template render3d.camera | ok |
|
||||||
| `comp.capsule` | engine | 16 | component physics.CapsuleCollider2D | ok |
|
| `comp.capsule` | engine | 16 | component physics.CapsuleCollider2D | ok |
|
||||||
| `comp.circle` | engine | 16 | component physics.CircleCollider2D | ok |
|
| `comp.circle` | engine | 16 | component physics.CircleCollider2D | ok |
|
||||||
| `comp.edge` | engine | 16 | component physics.EdgeCollider2D | ok |
|
| `comp.edge` | engine | 16 | component physics.EdgeCollider2D | ok |
|
||||||
| `comp.follow` | engine | 16 | component camera.CameraFollow2D; template camera.follow | ok |
|
|
||||||
| `comp.joint_distance` | engine | 16 | component physics.DistanceJoint2D | ok |
|
| `comp.joint_distance` | engine | 16 | component physics.DistanceJoint2D | ok |
|
||||||
| `comp.joint_revolute` | engine | 16 | Revolute Joint 2D component; Joints debug overlay; component physics.RevoluteJoint2D | ok |
|
| `comp.joint_revolute` | engine | 16 | Revolute Joint 2D component; Joints debug overlay; component physics.RevoluteJoint2D | ok |
|
||||||
| `comp.mesh` | engine | 16 | component engine.MeshRenderer | ok |
|
| `comp.mesh` | engine | 16 | component engine.MeshRenderer | ok |
|
||||||
@@ -37,11 +36,8 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
|
|||||||
| `comp.polygon` | engine | 16 | Polygon shape component; Shapes debug overlay; component physics.PolygonCollider2D | ok |
|
| `comp.polygon` | engine | 16 | Polygon shape component; Shapes debug overlay; component physics.PolygonCollider2D | ok |
|
||||||
| `comp.rigidbody` | engine | 16 | component physics.Body2D; template physics.rigidbody | ok |
|
| `comp.rigidbody` | engine | 16 | component physics.Body2D; template physics.rigidbody | ok |
|
||||||
| `comp.script` | engine | 16 | Scripts component; Assets menu: Attach to Selected; component engine.Script | ok |
|
| `comp.script` | engine | 16 | Scripts component; Assets menu: Attach to Selected; component engine.Script | ok |
|
||||||
| `comp.shake` | engine | 16 | component camera.CameraShake2D | ok |
|
|
||||||
| `comp.sprite` | engine | 16 | component engine.SpriteRenderer; template render2d.sprite | ok |
|
| `comp.sprite` | engine | 16 | component engine.SpriteRenderer; template render2d.sprite | ok |
|
||||||
| `comp.tags` | engine | 16 | component engine.Tags | ok |
|
| `comp.tags` | engine | 16 | component engine.Tags | ok |
|
||||||
| `comp.vcam` | engine | 16 | component camera.VirtualCamera2D | ok |
|
|
||||||
| `comp.zone` | engine | 16 | component camera.CameraZone2D | ok |
|
|
||||||
| `dbg.aabbs` | engine | 16 | AABBs debug overlay | ok |
|
| `dbg.aabbs` | engine | 16 | AABBs debug overlay | ok |
|
||||||
| `dbg.broadphase` | engine | 16 | Broadphase Tree debug overlay | ok |
|
| `dbg.broadphase` | engine | 16 | Broadphase Tree debug overlay | ok |
|
||||||
| `dbg.contacts` | engine | 16 | Contacts debug overlay; Vectors → Contact Impulses | ok |
|
| `dbg.contacts` | engine | 16 | Contacts debug overlay; Vectors → Contact Impulses | ok |
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 159 KiB |
|
Before Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 122 KiB |
@@ -231,8 +231,8 @@ func (p *Panel) link(list []richtext.Span) {
|
|||||||
for i := range list {
|
for i := range list {
|
||||||
sp := &list[i]
|
sp := &list[i]
|
||||||
if sp.Style == richtext.Code && sp.Target == "" {
|
if sp.Style == richtext.Code && sp.Target == "" {
|
||||||
if e, ok := p.reg.Link(sp.Text); ok {
|
if _, ok := p.reg.Get(sp.Text); ok {
|
||||||
sp.Target = "ref:" + e.ID
|
sp.Target = "ref:" + sp.Text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,10 +234,6 @@ func (a *App) Profiler() *profiler.Panel {
|
|||||||
return a.profiler
|
return a.profiler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Prefabs() *prefabs.Service {
|
|
||||||
return a.prefabs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) Viewport() *services.Viewport {
|
func (a *App) Viewport() *services.Viewport {
|
||||||
return a.viewport
|
return a.viewport
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const CoreID = "engine"
|
|||||||
type Core struct{}
|
type Core struct{}
|
||||||
|
|
||||||
func (Core) Info() module.Info {
|
func (Core) Info() module.Info {
|
||||||
return module.Info{ID: CoreID, Version: engine.Version, Builtin: true, DisplayName: "Core", DocsOrder: 0, DocsIcon: "cat.node"}
|
return module.Info{ID: CoreID, Version: engine.Version, Builtin: true, DisplayName: "Core", DocsOrder: 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Core) Register(r *module.Registry) error {
|
func (Core) Register(r *module.Registry) error {
|
||||||
|
|||||||