16 Commits
Author SHA1 Message Date
sanspie 6a22396467 Stage 9 complete
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / checks (push) Successful in 22m2s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 23m5s
2026-10-20 14:06:22 +03:00
sanspie 21e03128a8 Stage 9.6d2: MCP server and external edits in the editor (D297, D300) 2026-10-20 13:56:45 +03:00
sanspie e5b1964f3e Stage 9.6d1: agent layer and CLI (D299) 2026-10-20 13:48:01 +03:00
sanspie cffa66514b Stage 9.6c2: prefab apply and data editing without the editor (D298)
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / checks (push) Successful in 21m2s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 22m8s
2026-10-20 13:05:10 +03:00
sanspie 2744cff481 Stage 9.6c1: project refs and refactorings without the editor (D298)
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 23m31s
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / checks (push) Successful in 21m57s
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
2026-10-20 12:12:18 +03:00
sanspie 91052a54db make lint: staticcheck the editor build
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / checks (push) Successful in 16m26s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 17m48s
2026-10-20 12:06:56 +03:00
sanspie 7f4d87ef2c Stage 9: gameplay toolkit, export and web, agent tooling 2026-10-20 12:06:56 +03:00
sanspie 6cc0673082 CI on master, docs auto-deploy and repo link, macOS build fixes
ci / checks (push) Successful in 13m36s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 14m40s
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
2026-10-16 12:10:43 +03:00
sanspie ab9b003457 Stage 8: physics and module boundaries 2026-10-15 19:16:09 +03:00
sanspie 41d949717d deploy: push-to-deploy docs site and playground
make site-headless builds the CLI without cgo (aego_nonative) and the
static site with the wasm playground. deploy/ holds the image, the
container nginx config (gzip_static with Vary), compose and deploy.sh
run by the post-receive hook on mainserv.

.gitignore ignores only the root site/ output; tools/gendocs/site was
never committed because of it.

Docs site: index and section summaries render code, bold and italic
instead of raw markdown; search.json gets the plain text. Page summaries
drop a leading list or quote marker. The nav toggle is three CSS bars
centred in a 32px button.

Playground: a loading overlay in the editor pane shows a spinner, a
progress bar and the downloaded size while aego.wasm streams in, then
'Starting engine' until the runtime is ready; a failed start keeps the
error on the overlay. docstool writes the wasm size and a version hash
into index.html; play.js, play.css, wasm_exec.js and aego.wasm are
referenced with ?v=<hash> so a deploy never mixes cached old files with
new ones.

Instruction budget: default 1M per callback (was 100k), playground 2M;
the donut needs ~120k per frame. Playground: software 3D donut example,
multi-line log entries render as monospace blocks, a fault is reported
once instead of also as a "script stopped" log line.

The compose project is named aego explicitly; the default name came from
the deploy/ directory and collided with other stacks on the host.
2026-10-14 22:40:20 +03:00
sanspie 69d19e501f Stage 7: documentation, help, playground; templates, aego new/bootstrap/icons, launcher
Documentation: engine/docs assembles one registry from the script
bindings, the schema, module docs and module-owned script paths; problems
are reported as warnings at build time. Module registries record doc
sources and script module owners; diag.Codes lists every diagnostic code.

Project bootstrap: project.defaultSpace ("2d"/"3d"), EnsureDirs creates
the default folders when the editor opens a project (reported in the
console), prefabs/ is a pipeline root. Native builds drop -trimpath so
binding locations resolve inside native/.

Editor files: create scene/prefab/script from menus, move/rename/duplicate
/delete with undo, script package retargeting on move, open scenes and
script panels follow renames, Save As / Rename Scene prompts, scene
picker, startup scene. Scoped shortcuts fall back to the global command
when disabled; Save As is Ctrl+Shift+S, Save All Ctrl+Alt+S.

Script editor: Go to Implementation (stub → native/ source), host
definitions always resolve to stubs, bracket match/mismatch states,
rainbow brackets (persisted), mono code font (ui-mono atlases, per-command
fonts in draw.List, PushFont/PopFont restored by Mark/Restore).

Tests: langsvc definition/implementation, bracket states, dispatch
fallback, script renames, font stack, editor file-op integration, golden
mono glyph and bracket references; depcheck rule for engine/docs.

Docs pipeline: engine, render2d and script/engine ship their own res/docs;
keyword hover text comes from the registry through manifest.Keywords;
project script symbols join the registry; aego docs export|check|build|
serve, --tool docs in native binaries, aego check --explain; app/docstool
and the tools/gendocs module (goldmark) build the static site.

Playground: app/playground + cmd/aego-playground (js/wasm, 3.3 MB gzip)
compile, highlight and run scripts in the browser; run blocks in the
language docs become examples. engine/builtin/headless is the module set
without render3d; workspace.Config.FS; StubFor/StubPaths; backends and the
script importer drop out of js builds.

VM: one frame pointer in the interpreter loop, direct host calls, panic
recovery once per run instead of per host call (Update2k -19%,
Schedule10k -24% against the previous head). bench-build/compare/profile
run pre-built, pinned, interleaved binaries.

Editor: structural bracket levels and current-block highlight, indent
guides, occurrence highlighting with write marks, multi-format clipboard
(copy/cut/paste assets, paste images as sprites or textures). gl33 probes
a renderable depth format and reports framebuffer failures by stage.

7 E:
- project templates empty/2d/2.5d/3d, app/projects as the single creation path (D148)
- launcher and recent projects (D149)
- orthographic Camera3D and Scene View (D150)
- reference states in the Inspector, ids.Short (D151)
- sceneio remaps node references when loading without KeepIDs (D152)
- exported ComponentRef<T> (D153)
- aego icons via module.Audit (D146)
2026-10-13 01:08:56 +03:00
sanspie cf68c71f05 Stage 6: coroutines, signals, ScriptRef, workspace CLI, editor tooling
Language: yield/signal/coroutine become keywords, start is contextual.
Coroutine methods (func Name() coroutine) with start and
yield / wait / frames / fixed / await; signals with Emit/Connect and
Connection handles; ScriptRef<T> and node.GetScript<T>() for fields,
plain methods and signals of other scripts. Diagnostics AES2050-AES2059.

Bytecode v3: ScriptRefs, XFields, XMethods and Connects tables resolved by
name in vm.Link, per-script signals, coroutine flag on functions; new
opcodes START/YIELD/EMIT/CONNECT/DISCONNECT/CONNECTED/COSTOP/CORUN/
SELFREF/GETSCRIPT/GETXF/SETXF/CALLR, checked by verify.

Runtime: script/coroutine scheduler (frames/fixed counters, time heap,
await lists; only due coroutines are visited) and script/signal registry
(snapshot per Emit, deferred removal, no allocations in steady state).
Hot reload cancels coroutines of changed scripts, rebinds connections by
name and signature, then runs Reloaded.

Tooling: script/build removed, aego check/test/dump run on the workspace
(test builds include _test.aes); langsvc completion, hover, signatures and
semantic tokens for the new syntax; editor highlighting, snippets with
placeholders, coroutine/signal profiler graphs and stats.

Docs (script.md sections 16-19, editor.md, ARCHITECTURE), test fixtures and
golden files, platformer enemies wired to the player, scriptreload demo,
and run.Config.FrameTime for deterministic headless fixed steps.

Modules (6.5): engine/module registry and ComponentBuilder are the one
registration path for engine subsystems and native code; render2d,
render3d and script/engine are modules over scene/frame, the script
component moved to scene/scriptcomp, and scene/components and
scene/systems are gone. Editor inspector, templates, picking shapes and
extension hooks are driven by descriptors and editor/builtins; sdk gains
Component/EditorHook; depcheck checks the module graph and the
aego_runtime graph.

Also: stop tracking built tool binaries; gl33 activates the scratch unit
before binding for texture updates.

Verification: typed component method adapters (Method0..2, Proc0..2)
with direct field access and no allocations; app.Build as the single
construction path for run and the editor; modules attach per scene
document; depcheck aego_editor pass; diagnostic codes in one place and
every code covered by a test; indexed coroutine heaps with eager
cancel; dead component references and nil intrinsics fault with
AES6004, host faults keep their code; GC roots of struct fields checked
in verify and vm.Link; 255-field limit; AES4010 for script packages
shadowing host modules; architecture registry D111-D147.

Golden: the harness renders on the thread that owns the GL context,
ReadPixels returns rows in memory order (render targets are top-down per
D17), and the first rendering references are committed.
2026-10-12 14:22:08 +03:00
sanspie 08f0b15e1e docs: update architecture spec and script reference 2026-10-08 13:31:21 +03:00
sanspie 17cb5e4291 Stage 2-5: assets, scene/ECS, editor, 3D, and Aego Script
Stage 2: render pipeline and UI toolkit over gpu/platform.
Stage 3: schema registry, asset pipeline + runtime loader, scene graph and ECS.
Stage 4: editor — docking, inspector, scene/game views, play mode, asset browser, console, profiler; 4.5 adds the 3D foundation (mixed 2D/3D spaces, meshes, depth).
Stage 5: Aego Script — compiler (lexer/parser/types/resolver/checker/asm/compiler), bytecode with verifier and register VM, instance runtime, stdlib, native bindings, and editor/CLI integration (aego run/edit/check/test/dump).
2026-10-08 13:17:28 +03:00
sanspie eb9ac1e589 Initial commit 2026-09-26 16:32:40 +03:00
sanspie dae4bc19e1 Stage 1: engine core, platform, gpu, vfs
Foundation for the aego engine.

core/    errs, log, handle arena, mathx, clock, stats
platform SDL3 backend plus a headless one, input state, events
gpu      device abstraction with gl33 and null backends
vfs      scheme-based virtual filesystem with overlay mounts
engine   builder, module registry, phase-ordered system loop, jobs
demos    basic, resize, input, headless; sandbox driver

OpenGL bindings are generated into gpu/gl33/gl by tools/gen-gl.sh,
so the module has no external dependencies.
2026-09-18 20:51:54 +03:00
215 changed files with 547 additions and 13138 deletions
-1
View File
@@ -5,4 +5,3 @@ site
exports
**/.aego
*.test
.claude
-36
View File
@@ -3,7 +3,6 @@ name: ci
on:
push:
branches: [master]
tags: ["v*"]
pull_request:
permissions:
@@ -113,38 +112,3 @@ jobs:
run: |
/tmp/aego check examples/native-rotator
/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
+3 -16
View File
@@ -502,12 +502,12 @@ The Stage 8 specification numbered these decisions D153, D155, D158, D159, D168
| 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. |
| 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. |
| 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. |
### 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. |
| 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). |
| 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 `![alt](shots/name.png)`; 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
@@ -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.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.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 |
### 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 |
| D299 | Agent layer and CLI | 9.6d1 |
| 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 |
---
+3 -25
View File
@@ -38,7 +38,7 @@ BENCH_PHYSICS_PKGS :=
.DEFAULT_GOAL := bootstrap
-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)
GL_OVERRIDE = MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330
@@ -212,7 +212,7 @@ site: cli
@$(TRY_SIZE_CHECK)
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
site-headless:
@@ -282,29 +282,7 @@ fuzz:
go test -run '^$$' -fuzz '^FuzzDecodeVerify$$' -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
# 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
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
runtime: bootstrap
go build -tags aego_runtime -o .cache/bin/aego-runtime ./cmd/aego-runtime
+1 -6
View File
@@ -6,7 +6,6 @@ import (
"aego/engine/module"
"aego/modules/accessibility"
"aego/modules/audio"
"aego/modules/camera"
"aego/modules/dataasset"
"aego/modules/debug"
"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() }}
}
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 {
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 {
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()}
}
+1 -1
View File
@@ -3,5 +3,5 @@ package builtin
import "aego/app/assembly"
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()}
}
-3
View File
@@ -2,8 +2,6 @@ package editors
import (
"aego/app/assembly"
"aego/modules/camera"
cameraeditor "aego/modules/camera/editor"
"aego/modules/debug"
debugeditor "aego/modules/debug/editor"
inputmod "aego/modules/input"
@@ -22,6 +20,5 @@ func With(c assembly.Catalog) assembly.Catalog {
With(render2d.ID, render2deditor.Hook).
With(render3d.ID, render3deditor.Hook).
With(physics.ID, physicseditor.Hook).
With(camera.ID, cameraeditor.Hook).
With(debug.ID, debugeditor.Hook)
}
-6
View File
@@ -4,14 +4,11 @@ import (
"aego/app/assembly"
"aego/engine/base"
"aego/engine/module"
"aego/modules/camera"
"aego/modules/debug"
inputmod "aego/modules/input"
"aego/modules/physics"
"aego/modules/render2d"
"aego/modules/render3d"
"aego/modules/scripting"
"aego/modules/tween"
"aego/script/stdlib"
"aego/script/vm"
)
@@ -26,9 +23,6 @@ func Catalog() assembly.Catalog {
}},
{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: 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() }},
}
}
-33
View File
@@ -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
}
+1 -15
View File
@@ -138,7 +138,7 @@ func TestF1Refs(t *testing.T) {
if err := os.WriteFile(path, []byte(probe), 0o644); err != nil {
t.Fatal(err)
}
reg, ws, err := build(p, nil, nil)
reg, ws, err := build(p, nil)
if err != nil {
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)
}
}
+4 -16
View File
@@ -9,7 +9,6 @@ import (
"aego/app/assembly"
"aego/app/assembly/builtin"
"aego/app/playground"
"aego/engine/docs"
"aego/engine/module"
scriptsystems "aego/modules/scripting/systems"
@@ -21,7 +20,7 @@ import (
"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()
if err != nil {
return nil, nil, err
@@ -42,7 +41,6 @@ func build(p *project.Project, mods []module.Module, diagnose func(string, *docs
return nil, nil, err
}
src := docs.SourcesOf(bm, types.Schema(), man)
src.Diagnose = diagnose
var ws *workspace.Workspace
if p != nil {
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) {
r, _, err := build(p, mods, nil)
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))
r, _, err := build(p, mods)
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)
}
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
for _, p := range append(reg.Check(strict), extra...) {
for _, p := range reg.Check(strict) {
level := "warn "
if p.Error {
level = "error"
+3 -15
View File
@@ -16,7 +16,6 @@ import (
"strings"
"time"
"aego/app/assembly/builtin"
"aego/engine"
"aego/engine/module"
"aego/project"
@@ -41,7 +40,7 @@ func (e *Env) export(path string) error {
}
return e.Native(args...)
}
reg, err := ExportRegistry(e.Project, e.Modules)
reg, err := Registry(e.Project, e.Modules)
if err != nil {
return err
}
@@ -72,7 +71,7 @@ func (e *Env) Check(strict bool) int {
fmt.Fprintln(e.Stderr, "aego:", err)
return 1
}
code := Check(reg, strict, e.Stdout, DiagnosticProblems()...)
code := Check(reg, strict, e.Stdout)
if CheckTry(reg, e.Stdout) > 0 && code == 0 {
code = 1
}
@@ -99,10 +98,7 @@ func (e *Env) git(args ...string) string {
}
func (e *Env) commit() string {
if c := e.git("rev-parse", "--short", "HEAD"); c != "" {
return c
}
return os.Getenv("AEGO_COMMIT")
return e.git("rev-parse", "--short", "HEAD")
}
func (e *Env) branch() string {
@@ -154,14 +150,6 @@ func (e *Env) Build(out string, play bool) error {
if c := e.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) {
args = append(args, "-hero", hero)
}
+2 -6
View File
@@ -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 {
return 2
}
build := Registry
if args[0] == "export" {
build = ExportRegistry
}
reg, err := build(p, mods)
reg, err := Registry(p, mods)
if err != nil {
fmt.Fprintln(stderr, "aego:", err)
return 1
@@ -38,7 +34,7 @@ func Tool(p *project.Project, args []string, stdout, stderr io.Writer, mods ...m
}
return 0
case "check":
return Check(reg, *strict, stdout, DiagnosticProblems()...)
return Check(reg, *strict, stdout)
}
fmt.Fprintf(stderr, "unknown docs command %q\n", args[0])
return 2
+4 -23
View File
@@ -7,7 +7,6 @@ import (
playmods "aego/app/assembly/playground"
"aego/asset"
"aego/asset/loader"
"aego/contracts/debugdraw"
"aego/core/log"
"aego/core/mathx"
edclip "aego/editor/core/clipboard"
@@ -21,7 +20,6 @@ import (
"aego/engine/base"
"aego/engine/module"
"aego/format/aetext"
"aego/modules/physics"
scriptsystems "aego/modules/scripting/systems"
"aego/platform"
"aego/render"
@@ -43,11 +41,9 @@ const (
maxSprites = 16384
gameShare = 0.6
stackBelow = 640
// inspectorMin keeps field names and values readable in narrow embeds.
inspectorMin = 400
maxLines = 200
defaultW = 960
defaultH = 440
maxLines = 200
defaultW = 960
defaultH = 440
)
var coreBlocks = []string{"window", "defaults"}
@@ -81,7 +77,6 @@ type App struct {
log *log.Logger
lines []Line
textOn bool
shown *engine.World
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},
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},
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) {
e := a.e
now := e.Platform().Now()
a.session.Tick(dt)
a.showPhysics()
ui := a.ui
ui.Time = now
m := e.Metrics()
+1 -1
View File
@@ -27,7 +27,7 @@ func tries(t *testing.T) []docs.Entry {
t.Fatal(err)
}
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))
}
return out
+5 -16
View File
@@ -7,7 +7,6 @@ import (
"strconv"
"aego/editor/core/docsession"
"aego/scene/sceneio"
)
type request struct {
@@ -19,11 +18,10 @@ type request struct {
}
type Reply struct {
Text string `json:"text,omitempty"`
Values map[string]string `json:"values,omitempty"`
Error string `json:"error,omitempty"`
State *docsession.State `json:"state,omitempty"`
Log []Line `json:"log,omitempty"`
Text string `json:"text,omitempty"`
Error string `json:"error,omitempty"`
State *docsession.State `json:"state,omitempty"`
Log []Line `json:"log,omitempty"`
}
func (a *App) Call(method string, payload []byte) Reply {
@@ -63,16 +61,7 @@ func (a *App) Call(method string, payload []byte) Reply {
return Reply{Error: err.Error()}
}
st := s.State()
return Reply{Text: string(s.Text()), Values: Values(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)
return Reply{Text: string(s.Text()), State: &st}
}
func (a *App) Reply(method, payload string) string {
-3
View File
@@ -154,9 +154,6 @@ func Run(p *project.Project, cfg Config, mods ...sdk.Module) error {
panic(rec)
}
}()
if dir := os.Getenv(EnvShots); dir != "" {
return s.shots(dir)
}
if err := s.Run(); err != nil {
return err
}
-155
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -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:hover { opacity: 1; }
</style>
<link rel="preload" href="game.wasm?v={{.Build}}" as="fetch" type="application/wasm" crossorigin>
</head>
<body>
<canvas id="game" data-aego tabindex="0" aria-label="{{.Title}}"></canvas>
-31
View File
@@ -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")
}
}
-65
View File
@@ -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, "; ")
}
-27
View File
@@ -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)
}
}
+10 -36
View File
@@ -101,16 +101,9 @@ type Playground struct {
proj *exampleProject
gameCfg GameConfig
game *Game
simEngine *engine.Engine
}
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()
if err != nil {
return nil, err
@@ -120,7 +113,7 @@ func NewWith(cat assembly.Catalog) (*Playground, error) {
return nil, err
}
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 {
return nil, err
}
@@ -212,23 +205,6 @@ func diagnostics(sm *source.Manager, ds []diag.Diagnostic) []Diagnostic {
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 {
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()
res := Result{Diagnostics: diagnostics(gen.Sources, ds)}
if len(gen.Failed) > 0 || diag.HasErrors(ds) {
res.Diagnostics = append(res.Diagnostics, blocked(gen.Failed)...)
return res
}
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.Input = &input.State{}
w.SetService(scene.ServiceID, s)
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()
ctx := engine.Context{World: w, Dt: 1.0 / 60}
for i := 0; i < frames; i++ {
frame = uint64(i + 1)
e.TickWorld(w, engine.ScopePlay, 1.0/60)
ctx.Frame++
frame = ctx.Frame
rt.RunDeferred()
rt.FixedUpdate(&ctx)
rt.Update(&ctx)
rt.Coroutines(&ctx)
s.Flush()
s.UpdateTransforms()
}
res.Frames = frames
for _, f := range rt.Faults() {
-43
View File
@@ -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
}
+1 -3
View File
@@ -3,7 +3,6 @@
package main
import (
"encoding/json"
"syscall/js"
"aego/app/docsview"
@@ -18,8 +17,7 @@ func main() {
} else {
app.OnChange = func(text string) {
if f := g.Get("aegoDocsChanged"); f.Type() == js.TypeFunction {
values, _ := json.Marshal(docsview.Values([]byte(text)))
f.Invoke(text, string(values))
f.Invoke(text)
}
}
api.Set("call", js.FuncOf(func(_ js.Value, args []js.Value) any {
-20
View File
@@ -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 -8
View File
@@ -1,19 +1,12 @@
FROM golang:1.27-alpine AS build
RUN apk add --no-cache make gzip brotli
RUN apk add --no-cache make gzip
WORKDIR /src
COPY go.mod go.sum ./
COPY tools/gendocs/go.mod tools/gendocs/go.sum tools/gendocs/
RUN go mod download && cd tools/gendocs && go mod download
COPY . .
ARG AEGO_COMMIT=""
ENV AEGO_COMMIT=$AEGO_COMMIT
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 '*.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
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
-2
View File
@@ -5,8 +5,6 @@ services:
build:
context: ..
dockerfile: deploy/Dockerfile
args:
AEGO_COMMIT: ${AEGO_COMMIT:-}
image: aego-site:latest
container_name: aego-site
restart: unless-stopped
+1 -1
View File
@@ -15,7 +15,7 @@ else
git checkout -q -f --detach "origin/$ref"
fi
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 image prune -f >/dev/null
echo "$sha" >"$state/deployed"
File diff suppressed because it is too large Load Diff
-87
View File
@@ -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
-8
View File
@@ -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
-9
View File
@@ -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 -40
View File
@@ -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 {
listen 80;
root /usr/share/nginx/html;
@@ -25,32 +10,8 @@ server {
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 / {
add_header Cache-Control $aego_cache;
add_header Cache-Control "public, max-age=300";
try_files $uri $uri.html $uri/ =404;
}
}
+1 -3
View File
@@ -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 |
| Scene data | presentation pose (`SetPresentationLocal`), component defaults, `OnAdd` |
| 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 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 |
| 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
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.
+1 -5
View File
@@ -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.implementation` | engine | 16 | Scripts → Go to Implementation | 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.capsule` | engine | 16 | component physics.CapsuleCollider2D | ok |
| `comp.circle` | engine | 16 | component physics.CircleCollider2D | 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_revolute` | engine | 16 | Revolute Joint 2D component; Joints debug overlay; component physics.RevoluteJoint2D | 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.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.shake` | engine | 16 | component camera.CameraShake2D | ok |
| `comp.sprite` | engine | 16 | component engine.SpriteRenderer; template render2d.sprite | 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.broadphase` | engine | 16 | Broadphase Tree debug overlay | ok |
| `dbg.contacts` | engine | 16 | Contacts debug overlay; Vectors → Contact Impulses | ok |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

+2 -2
View File
@@ -231,8 +231,8 @@ func (p *Panel) link(list []richtext.Span) {
for i := range list {
sp := &list[i]
if sp.Style == richtext.Code && sp.Target == "" {
if e, ok := p.reg.Link(sp.Text); ok {
sp.Target = "ref:" + e.ID
if _, ok := p.reg.Get(sp.Text); ok {
sp.Target = "ref:" + sp.Text
}
}
}
-4
View File
@@ -234,10 +234,6 @@ func (a *App) Profiler() *profiler.Panel {
return a.profiler
}
func (a *App) Prefabs() *prefabs.Service {
return a.prefabs
}
func (a *App) Viewport() *services.Viewport {
return a.viewport
}
+1 -1
View File
@@ -18,7 +18,7 @@ const CoreID = "engine"
type Core struct{}
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 {

Some files were not shown because too many files have changed in this diff Show More