60 Commits
Author SHA1 Message Date
sanspie 6230d30620 docs: light code theme, reference icons for script packages and settings, no code ligatures
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) Failing after 10m39s
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 11m41s
ci / release (push) Skipped
2026-10-21 17:00:56 +03:00
sanspie 91ea202e74 docs: inline CSS so Firefox paints dark at once, header on the page grid, readable fields table
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 / build (amd64, linux, ubuntu-24.04) (push) Successful in 17m28s
ci / checks (push) Successful in 17m7s
ci / release (push) Successful in 27m25s
2026-10-21 16:34:01 +03:00
sanspie 6128c86685 docs: editor icons back, editor theme colors, no white flash or sidebar jump on load
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 16m46s
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 14m12s
ci / release (push) Skipped
2026-10-21 16:21:51 +03:00
sanspie 2be6ce1d64 docs: self-host IBM Plex Sans and JetBrains Mono
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 14m44s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 16m44s
ci / release (push) Successful in 19m19s
2026-10-21 16:12:07 +03:00
sanspie 2e664ae508 docs: skip image-only lines when picking a page summary
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 15m19s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 17m41s
ci / release (push) Successful in 19m50s
2026-10-21 16:05:34 +03:00
sanspie aab6e0b0c9 docs: quieter site design, flat nav for single-page groups, drawer on mobile, light theme toggle
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci / checks (push) Successful in 16m48s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 18m53s
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / release (push) Successful in 22m38s
2026-10-21 15:57:53 +03:00
sanspie 9ba3e1631f docs: Try it demos for every component, with scripts, prefabs and in-memory saves
ci / release (push) Successful in 36m23s
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 21m16s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 23m8s
2026-10-21 13:11:16 +03:00
sanspie 4068e00375 install: read SHA256SUMS.txt first and fetch the archive by its checksum, past stale proxy caches
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / checks (push) Successful in 15m44s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 17m13s
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / release (push) Successful in 17m39s
2026-10-21 03:56:04 +03:00
sanspie 914cf39150 release: Linux files and checksums first, keep going when a Windows upload fails
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 / release (push) Successful in 20m29s
ci / checks (push) Successful in 14m57s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 16m18s
2026-10-21 02:37:55 +03:00
sanspie dede4d8af3 release: record upload failures in the release body
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 13m53s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 15m0s
ci / release (push) Failing after 15m2s
2026-10-21 02:32:02 +03:00
sanspie b6dd46f523 Stage 10a follow-up: camera scene tests, rewind check, value Adjust
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / checks (push) Successful in 14m14s
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 16m1s
ci / release (push) Failing after 14m38s
- camera.NewService and SetAccessibility; AttachWorld wires the accessibility settings
- tests/camera: priorities, blends, cuts, zones, follow, bounds, outputs, shake, zero allocations, BrainStep benchmark
- integration: rewind restores the camera state across a zone change
- camera2d.Adjuster passes the view by value (no per-frame allocation), D304 updated
- modules/camera/module.mk: test-camera in CI, bench-camera
2026-10-21 01:32:28 +03:00
sanspie 9d32052624 release: one SHA256SUMS.txt and a zipped Windows installer, the attachment types Gitea accepts
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / release (push) Failing after 14m32s
ci / checks (push) Successful in 14m14s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 16m8s
2026-10-21 01:31:53 +03:00
sanspie e636801578 release: upload straight to the Gitea server with retries
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 / release (push) Failing after 14m22s
ci / checks (push) Successful in 14m30s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 17m14s
2026-10-21 00:29:58 +03:00
sanspie 01fdeef7a8 Aego 0.9.0-beta.1
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 14m2s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 16m23s
ci / release (push) Failing after 14m1s
2026-10-20 23:57:45 +03:00
sanspie 71354f6a60 install scripts: fall back to nightly, explain when nothing is published 2026-10-20 23:55:52 +03:00
sanspie 8ae6832503 edge sync: keep the destination when resuming downloads 2026-10-20 20:11:47 +03:00
sanspie 183d75ed50 gendocs: nav golden without the editor-only tour 2026-10-20 20:07:22 +03:00
sanspie 19edcbf9b2 render3d in the web sandbox without the OBJ importer; 3D try scenes select a shape 2026-10-20 19:49:47 +03:00
sanspie 8ed77a7638 Docs: no favicon request 2026-10-20 19:48:24 +03:00
sanspie ca3c9bf2e7 Docs: 3D Try it scenes, render3d in the sandbox, smarter code language detection 2026-10-20 19:43:38 +03:00
sanspie db59f117a4 deploy: short cache for the root page; edge notes in D303 2026-10-20 19:35:50 +03:00
sanspie 0d2c20da26 Docs: revision in the header, content-versioned assets; deploy passes the commit to the image 2026-10-20 19:31:14 +03:00
sanspie dd41ff15a5 deploy: edge serves the site and wasm from its own copy 2026-10-20 19:12:22 +03:00
sanspie 599e09a35d Docs: Try it on Physics 2D guide pages 2026-10-20 18:26:35 +03:00
sanspie 60614938be Release: Windows installer, install.ps1, component page layout 2026-10-20 18:20:18 +03:00
sanspie a60ca47d31 wip release windows 2026-10-20 18:13:57 +03:00
sanspie 00ae2418a0 Docs: more editor screenshots for the editor, prefab, tags, input, time and physics guides 2026-10-20 17:57:58 +03:00
sanspie 8fc9b4c287 Docs: highlighting for every code block, editor-only tour off the site, page icons, prefetch, field docs for physics, wider Try it, demo camera 2026-10-20 17:50:21 +03:00
sanspie b29934b51d deploy: short HTML cache the edge proxy can store 2026-10-20 17:29:55 +03:00
sanspie c475b42e5d gendocs: Script API index links to type pages; nav golden 2026-10-20 17:01:11 +03:00
sanspie b103816400 websmoke: Try smoke checks the scene tree 2026-10-20 17:00:46 +03:00
sanspie 487170dd9c depcheck: tween in the web sandbox 2026-10-20 16:46:52 +03:00
sanspie 57579356d6 Docs: streamed compile with tee'd progress, tween in the sandbox, console runs step module systems 2026-10-20 16:46:03 +03:00
sanspie d5377410e4 Docs: HTTP cache for the docs runtime, autostart after a first start, cheaper warm loads 2026-10-20 16:29:16 +03:00
sanspie f6c83a6e0c Docs: compile-checked examples for modules outside the sandbox, HasScript fix 2026-10-20 16:03:58 +03:00
sanspie 8d7712d671 Docs: runnable Script API module examples and Run it blocks 2026-10-20 16:03:26 +03:00
sanspie 63980faccb Docs: diagnostics with checked examples 2026-10-20 16:00:13 +03:00
sanspie 0041e7e639 Docs: loader streams compile from the original response 2026-10-20 15:59:50 +03:00
sanspie e334be569f Architecture: D301–D303, amendments for the docs overhaul 2026-10-20 15:49:46 +03:00
sanspie b488958444 Docs panel goldens with short-name links 2026-10-20 15:48:31 +03:00
sanspie 5875a65193 Docs: Try it for every 2D physics component, script examples on component pages, Script API index, smooth wheel scrolling on the web 2026-10-20 15:46:33 +03:00
sanspie 6ab66cb8ae Docs: scene tree, loading progress and cache, release builds, install page, editor screenshots 2026-10-20 15:36:03 +03:00
sanspie f8fe54e9c5 Docs: short-name cross references in the site and the editor docs panel, links from code 2026-10-20 15:17:10 +03:00
sanspie e4326e7e7c Docs: editor icons, editor highlighter, redesigned site and navigation 2026-10-20 15:14:55 +03:00
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
54 changed files with 2175 additions and 519 deletions
+1 -1
View File
@@ -507,7 +507,7 @@ The Stage 8 specification numbered these decisions D153, D155, D158, D159, D168
| 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. |
| 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 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. Amended: a try scene may bring `<Name>.aes` and `<Name>.<prefab>.aeprefab` (id from its `.meta`). `aego docs export` compiles the script with the playground compiler into `TryCode` (base64 bytecode modules); the page passes code and prefabs to `load`, the docs runtime publishes the script types before parsing the scene and mounts the prefabs into its loader from memory. The sandbox includes `spawn` and `savegame` with an in-memory save store, and a play controller whose scene has no asset id reloads that scene for `save.Load`. Every component has a try scene; `TestTryScenesRun` runs each through `load` for 400 frames. |
| 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
+4
View File
@@ -10,7 +10,9 @@ import (
"aego/modules/physics"
"aego/modules/render2d"
"aego/modules/render3d"
"aego/modules/savegame"
"aego/modules/scripting"
"aego/modules/spawn"
"aego/modules/tween"
"aego/script/stdlib"
"aego/script/vm"
@@ -29,6 +31,8 @@ func Catalog() assembly.Catalog {
{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() }},
{ID: spawn.ID, New: func(assembly.Options) module.Module { return spawn.New() }},
{ID: savegame.ID, Requires: []string{scripting.ID, spawn.ID}, New: func(assembly.Options) module.Module { return savegame.New() }},
}
}
+61
View File
@@ -0,0 +1,61 @@
package playground
import (
"io/fs"
"path"
"sort"
"strings"
"time"
"aego/contracts/persistence"
"aego/engine"
"aego/platform"
"aego/save"
)
// UseMemorySaves gives e a save store kept in memory, for runs in a browser page that must not touch the visitor's storage.
func UseMemorySaves(e *engine.Engine) {
e.SetService(persistence.StoreID, save.NewStoreFS("saves", &memUser{files: map[string][]byte{}}))
}
type memUser struct {
files map[string][]byte
}
func (m *memUser) ReadFile(p string) ([]byte, error) {
data, ok := m.files[path.Clean(p)]
if !ok {
return nil, fs.ErrNotExist
}
return append([]byte(nil), data...), nil
}
func (m *memUser) WriteFile(p string, data []byte) error {
m.files[path.Clean(p)] = append([]byte(nil), data...)
return nil
}
func (m *memUser) Remove(p string) error {
delete(m.files, path.Clean(p))
return nil
}
func (m *memUser) Stat(p string) (platform.UserEntry, error) {
data, ok := m.files[path.Clean(p)]
if !ok {
return platform.UserEntry{}, fs.ErrNotExist
}
return platform.UserEntry{Name: path.Base(p), Size: int64(len(data)), Mod: time.Time{}}, nil
}
func (m *memUser) List(dir string) ([]platform.UserEntry, error) {
prefix := path.Clean(dir) + "/"
var out []platform.UserEntry
for p, data := range m.files {
if rest, ok := strings.CutPrefix(p, prefix); ok && !strings.Contains(rest, "/") {
out = append(out, platform.UserEntry{Name: rest, Size: int64(len(data))})
}
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out, nil
}
+62
View File
@@ -0,0 +1,62 @@
package playground
import (
"encoding/base64"
"strings"
"aego/asset"
"aego/asset/loader"
"aego/core/ids"
"aego/script/bytecode"
"aego/vfs"
"aego/vfs/memfs"
)
// EncodeTry packs the compiled script of a try scene for a docs page: base64 modules joined by commas.
func EncodeTry(mods []*bytecode.Module) string {
parts := make([]string, len(mods))
for i, m := range mods {
parts[i] = base64.StdEncoding.EncodeToString(bytecode.Encode(m))
}
return strings.Join(parts, ",")
}
func DecodeTry(code string) ([]*bytecode.Module, error) {
if code == "" {
return nil, nil
}
var mods []*bytecode.Module
for _, part := range strings.Split(code, ",") {
b, err := base64.StdEncoding.DecodeString(part)
if err != nil {
return nil, err
}
m, err := bytecode.Decode(b)
if err != nil {
return nil, err
}
mods = append(mods, m)
}
return mods, nil
}
// MountTry gives the loader the prefabs of a try scene, keyed by asset id; none unmounts them.
func MountTry(l *loader.Loader, prefabs map[string]string) error {
idx := asset.NewIndex()
files := map[string][]byte{}
for key, text := range prefabs {
id, err := ids.ParseAssetID(key)
if err != nil {
return err
}
p := id.String() + ".aeprefab"
files[p] = []byte(text)
idx.Put(asset.Entry{ID: id, Kind: asset.ArtPrefab, Name: p, Source: p,
Artifacts: []asset.Artifact{{Kind: asset.ArtPrefab, Path: "try://" + p}}})
}
fs, err := vfs.New(vfs.Mount{Scheme: "try", ReadOnly: true, Source: memfs.New(files)})
if err != nil {
return err
}
return l.Mount(idx, fs)
}
+8 -4
View File
@@ -21,7 +21,8 @@ import (
"aego/script/workspace"
)
func build(p *project.Project, mods []module.Module, diagnose func(string, *docs.DiagInfo)) (*docs.Registry, *workspace.Workspace, error) {
// build assembles the registry; with pg it also compiles the diagnostics examples and the try scripts.
func build(p *project.Project, mods []module.Module, pg *playground.Playground) (*docs.Registry, *workspace.Workspace, error) {
types, err := assembly.NewTypes()
if err != nil {
return nil, nil, err
@@ -42,7 +43,10 @@ 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
if pg != nil {
src.Diagnose = diagnoser(pg)
src.CompileTry = pg.TryCode
}
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})
@@ -67,13 +71,13 @@ func Registry(p *project.Project, mods []module.Module) (*docs.Registry, error)
return r, err
}
// ExportRegistry is Registry with the diagnostics examples compiled for their messages and positions.
// ExportRegistry is Registry with the diagnostics examples compiled for their messages and positions, and the try scripts compiled.
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, pg)
return r, err
}
+8 -1
View File
@@ -41,9 +41,16 @@ func CheckTry(reg *docs.Registry, w io.Writer) int {
}
msgs = append(msgs, msg)
}
switch res := pg.PlayScene(e.ID, []byte(e.Try)); {
var code string
var err error
if e.TryScript != "" {
code, err = pg.TryCode(e.TryScript)
}
switch res := pg.PlayScene(e.ID, []byte(e.Try), code, e.TryPrefabs); {
case !sandbox[e.ID]:
add("belongs to a module the docs runtime does not include")
case err != nil:
add("script does not compile: " + err.Error())
case !res.Started:
add("does not load: " + res.Error)
default:
+4 -1
View File
@@ -75,6 +75,7 @@ type App struct {
ui *core.Context
clip *docsession.Clipboard
session *docsession.Session
scripts *scriptHost
insp *inspector.Panel
game *gameview.Panel
list *draw.List
@@ -143,15 +144,17 @@ func New(opt Options) (*App, error) {
return nil, err
}
a.e.SetService(loader.ServiceID, a.loader)
playmods.UseMemorySaves(a.e)
a.e.SetService(renderhost.ServiceID, a.renderer)
if err := a.man.Init(&module.InitContext{Engine: a.e, Assets: a.loader, Log: a.log, Editor: true, Sections: sections}); err != nil {
a.Close()
return nil, err
}
a.initUI()
a.scripts = &scriptHost{reg: reg, types: a.types, man: a.man, log: a.log}
a.session = docsession.New(docsession.Options{
Engine: a.e, Renderer: a.renderer, Loader: a.loader, Types: a.types, Sections: sections,
Scripts: &scriptHost{reg: reg, types: a.types, man: a.man, log: a.log},
Scripts: a.scripts,
UI: a.ui,
Clipboard: a.clip,
Log: a.log,
+15 -3
View File
@@ -3,6 +3,7 @@
package docsview
import (
"encoding/json"
"strings"
"testing"
@@ -46,11 +47,22 @@ func errorsOf(a *App) []string {
func TestTryScenesRun(t *testing.T) {
a := newHeadless(t)
p, err := playground.New()
if err != nil {
t.Fatal(err)
}
for _, e := range tries(t) {
if err := a.session.Load([]byte(e.Try), e.TrySelect); err != nil {
t.Fatalf("%s: %v", e.ID, err)
var code string
if e.TryScript != "" {
if code, err = p.TryCode(e.TryScript); err != nil {
t.Fatalf("%s: %v", e.ID, err)
}
}
if err := a.Step(60); err != nil {
req, _ := json.Marshal(map[string]any{"scene": e.Try, "select": e.TrySelect, "code": code, "prefabs": e.TryPrefabs})
if r := a.Call("load", req); r.Error != "" {
t.Fatalf("%s: %s", e.ID, r.Error)
}
if err := a.Step(400); err != nil {
t.Fatalf("%s: %v", e.ID, err)
}
st := a.session.State()
+18 -6
View File
@@ -6,16 +6,19 @@ import (
"encoding/json"
"strconv"
playmods "aego/app/assembly/playground"
"aego/editor/core/docsession"
"aego/scene/sceneio"
)
type request struct {
Scene string `json:"scene"`
Select string `json:"select"`
Component string `json:"component"`
Field string `json:"field"`
Value float64 `json:"value"`
Scene string `json:"scene"`
Select string `json:"select"`
Code string `json:"code"`
Prefabs map[string]string `json:"prefabs"`
Component string `json:"component"`
Field string `json:"field"`
Value float64 `json:"value"`
}
type Reply struct {
@@ -37,7 +40,16 @@ func (a *App) Call(method string, payload []byte) Reply {
var err error
switch method {
case "load":
err = s.Load([]byte(req.Scene), req.Select)
mods, derr := playmods.DecodeTry(req.Code)
if err = derr; err == nil {
err = a.scripts.use(mods)
}
if err == nil {
err = playmods.MountTry(a.loader, req.Prefabs)
}
if err == nil {
err = s.Load([]byte(req.Scene), req.Select)
}
case "reset":
err = s.Reset()
case "text":
+9
View File
@@ -22,6 +22,15 @@ type scriptHost struct {
mods []*bytecode.Module
}
// use makes mods the scripts of the next scene and publishes their types, so the scene keeps their fields.
func (h *scriptHost) use(mods []*bytecode.Module) error {
if err := scriptruntime.Publish(h.types.Schema(), mods, h.reg.Surface()); err != nil {
return err
}
h.mods = mods
return nil
}
func (h *scriptHost) Errors() int {
return 0
}
+1
View File
@@ -152,6 +152,7 @@ func newGame(cfg GameConfig, types *scene.Types, reg *bind.Registry, man *module
return nil, err
}
e.SetService(loader.ServiceID, g.loader)
playmods.UseMemorySaves(e)
e.SetService(renderhost.ServiceID, g.renderer)
e.SetView(frame.ServiceID, g.runner)
e.MainWorld().Recorder = recorder.New(recordDepth)
+31 -2
View File
@@ -1,10 +1,13 @@
package playground
import (
"errors"
"fmt"
"path"
"sort"
"strings"
playmods "aego/app/assembly/playground"
"aego/script/bytecode"
"aego/script/diag"
)
@@ -110,13 +113,39 @@ func (p *Playground) LoseContext(lose bool) bool {
return p.game != nil && p.game.LoseContext(lose)
}
func (p *Playground) PlayScene(name string, src []byte) PlayResult {
func (p *Playground) PlayScene(name string, src []byte, code string, prefabs map[string]string) PlayResult {
mods, err := playmods.DecodeTry(code)
if err != nil {
return PlayResult{Error: err.Error()}
}
g, err := p.host()
if err != nil {
return PlayResult{Error: err.Error()}
}
if err := g.Play(name, src, nil); err != nil {
if err := playmods.MountTry(g.loader, prefabs); err != nil {
return PlayResult{Error: err.Error()}
}
if err := g.Play(name, src, mods); err != nil {
return PlayResult{Error: err.Error()}
}
return PlayResult{Started: true}
}
// TryCode compiles the script of a try scene into the form DecodeTry reads.
func (p *Playground) TryCode(script string) (string, error) {
placed, err := place(map[string]string{"main.aes": script})
if err != nil {
return "", err
}
p.ws.Sync(placed)
gen, ds := p.ws.Build()
if len(gen.Failed) > 0 || diag.HasErrors(ds) {
for _, d := range diagnostics(gen.Sources, ds) {
if d.Severity == "error" {
return "", fmt.Errorf("%s:%d:%d: %s", d.File, d.Line, d.Col, d.Message)
}
}
return "", errors.New("the script does not build")
}
return playmods.EncodeTry(ordered(gen.Modules)), nil
}
+10 -4
View File
@@ -75,12 +75,18 @@ func TestSandboxHeapLimit(t *testing.T) {
}
}
func TestSandboxHasNoSaves(t *testing.T) {
// The sandbox has aego/save for the Persist demo; its saves stay in memory, never in the visitor's storage.
func TestSandboxSavesStayInMemory(t *testing.T) {
p := newHeadless(t)
for _, d := range p.Compile(map[string]string{"main.aes": saveScript}) {
res := p.Run(map[string]string{"main.aes": saveScript}, 10)
for _, d := range res.Diagnostics {
if d.Severity == "error" {
return
t.Fatalf("aego/save does not compile in the sandbox: %s", d.Message)
}
}
for _, l := range res.Log {
if strings.Contains(l.Text, "saves are not available") {
t.Fatal("the sandbox has no save store")
}
}
t.Fatal("aego/save compiled in the sandbox")
}
+14 -10
View File
@@ -295,16 +295,20 @@ func (c *Controller) SceneID() ids.AssetID {
func (c *Controller) LoadScene(id ids.AssetID) error {
ctx := c.ctx
if ctx.Pipeline == nil {
return errs.New(errs.EditorDocument, "the asset pipeline is not available")
}
rel, ok := ctx.Pipeline.PathOf(id)
if !ok {
return errs.New(errs.EditorDocument, "the saved scene is not in the project")
}
d, err := ctx.Docs.Open(rel)
if err != nil {
return err
d := ctx.Docs.Get(c.doc)
// a scene that is not an asset, such as a docs Try it scene, saves with a zero id and reloads itself
if !id.IsZero() || d == nil {
if ctx.Pipeline == nil {
return errs.New(errs.EditorDocument, "the asset pipeline is not available")
}
rel, ok := ctx.Pipeline.PathOf(id)
if !ok {
return errs.New(errs.EditorDocument, "the saved scene is not in the project")
}
var err error
if d, err = ctx.Docs.Open(rel); err != nil {
return err
}
}
held := c.held
if err := c.Start(d); err != nil {
+7
View File
@@ -94,3 +94,10 @@ func TestScriptDocsNeedRunBlocks(t *testing.T) {
t.Fatalf("a project page is held to the run block rule: %v", got)
}
}
func TestSummarySkipsImages(t *testing.T) {
got := summary("# Editor\n\n![The Design workspace](shots/editor.png)\n\nThe editor has workspaces.\n")
if got != "The editor has workspaces." {
t.Fatalf("summary = %q", got)
}
}
+1 -1
View File
@@ -262,7 +262,7 @@ func summary(md string) string {
continue
}
t := strings.TrimSpace(l)
if fence || t == "" || strings.HasPrefix(t, "#") || strings.HasPrefix(t, "|") {
if fence || t == "" || strings.HasPrefix(t, "#") || strings.HasPrefix(t, "|") || strings.HasPrefix(t, "![") && strings.HasSuffix(t, ")") {
continue
}
return unwrap(trimMarker(t))
+55 -1
View File
@@ -5,6 +5,7 @@ package docs
import (
"io/fs"
"path"
"regexp"
"slices"
"sort"
"strconv"
@@ -50,6 +51,7 @@ const (
unknownOrder = 1000
TryDir = "try"
TryExt = ".aescene"
TryScriptExt = ".aes"
)
var kindNames = [...]string{"page", "module", "function", "type", "field", "method", "signal", "code", "const", "enum"}
@@ -107,7 +109,12 @@ type Entry struct {
Related []string
Try string
TrySelect string
Icon string
// TryScript is the script next to the try scene; TryCode is its compiled bytecode, base64 modules joined by commas.
TryScript string `json:",omitempty"`
TryCode string `json:",omitempty"`
// TryPrefabs are the prefabs next to the try scene, by asset id.
TryPrefabs map[string]string `json:",omitempty"`
Icon string
// EditorOnly pages are shown in the editor's docs panel and left out of the site.
EditorOnly bool `json:",omitempty"`
Diag *DiagInfo
@@ -156,6 +163,8 @@ type Sources struct {
Settings []settings.Desc
EventOwners map[string]string
Diagnose func(code string, d *DiagInfo)
// CompileTry turns the script of a try scene into the bytecode the docs runtime loads.
CompileTry func(script string) (string, error)
}
type pageMeta struct {
@@ -269,6 +278,7 @@ func Assemble(src Sources) *Registry {
r.pages(d)
r.tries(d)
}
r.compileTries(src.CompileTry)
r.index()
r.related()
return r
@@ -854,6 +864,49 @@ func (r *Registry) pageTry(src module.DocSource, rec record) {
return
}
r.entries[i].Try, r.entries[i].TrySelect = string(data), rec.trySelect
r.tryExtras(&r.entries[i], src, rec.try)
}
var metaID = regexp.MustCompile(`\bid="([0-9a-f]{32})"`)
// tryExtras reads the script X.aes and the prefabs X.<name>.aeprefab, ids from their .meta, next to the try scene X.aescene.
func (r *Registry) tryExtras(e *Entry, src module.DocSource, scenePath string) {
base := strings.TrimSuffix(path.Clean(scenePath), TryExt)
if data, err := fs.ReadFile(src.FS, base+TryScriptExt); err == nil {
e.TryScript = string(data)
}
files, _ := fs.Glob(src.FS, base+".*.aeprefab")
for _, f := range files {
text, err := fs.ReadFile(src.FS, f)
meta, merr := fs.ReadFile(src.FS, f+".meta")
m := metaID.FindSubmatch(meta)
if err != nil || merr != nil || m == nil {
r.problems = append(r.problems, Problem{ID: e.ID, Error: true, Message: "try prefab " + f + " needs a .meta with its id"})
continue
}
if e.TryPrefabs == nil {
e.TryPrefabs = map[string]string{}
}
e.TryPrefabs[string(m[1])] = string(text)
}
}
func (r *Registry) compileTries(compile func(string) (string, error)) {
if compile == nil {
return
}
for i := range r.entries {
e := &r.entries[i]
if e.TryScript == "" {
continue
}
code, err := compile(e.TryScript)
if err != nil {
r.problems = append(r.problems, Problem{ID: e.ID, Error: true, Message: "try script does not compile: " + err.Error()})
continue
}
e.TryCode = code
}
}
func (r *Registry) componentNamed(short, owner string) (int, bool) {
@@ -892,5 +945,6 @@ func (r *Registry) tries(src module.DocSource) {
}
e := &r.entries[i]
e.Try, e.TrySelect = string(data), e.ID
r.tryExtras(e, src, f)
}
}
+5 -1
View File
@@ -45,7 +45,7 @@ Each frame the brain picks one virtual camera per output:
The pose comes from the virtual camera's node: its interpolated position and rotation, and [Zoom](ref:camera.VirtualCamera2D.Zoom), where 0 keeps the zoom of the Camera 2D. A Camera Follow 2D on the same node replaces the position. The node's transform is never changed; the camera only moves the view.
In Try it, select **Close** and set its Priority to 1: the view blends to it. Set it back to -1 and the view blends back.
In Try it, the Director script raises **Close**, then **Tilt**, for two seconds each, and the view blends to them and back. Change Zoom, Blend Time or Blend Curve of Close in the Inspector and watch the next blend; the script is under the scene.
## Blends
@@ -118,6 +118,8 @@ In Try it, the purple area in the middle of the ramp is the zone: the view blend
---
id: camera/shake
try: try/CameraShake2D.aescene
try_select: camera.CameraShake2D
icon: comp.shake
parent: camera
order: 5
@@ -125,6 +127,8 @@ title: Shake
---
# Shake
In Try it, the crate drops every two seconds and its script calls `camera.Shake(0.9)` when it lands. Raise Max Angle or lower Exponent and Decay on the camera to feel the difference.
Shake is driven by trauma, a value from 0 to 1 per output. `camera.Shake(0.4)` adds trauma, which fades by [Decay](ref:camera.CameraShake2D.Decay) per second. The view moves by up to [Max Offset](ref:camera.CameraShake2D.MaxOffset) and turns by up to [Max Angle](ref:camera.CameraShake2D.MaxAngle), scaled by trauma to the power [Exponent](ref:camera.CameraShake2D.Exponent), so small hits stay subtle. Without a Camera Shake 2D on the active camera the defaults are used.
The motion comes from deterministic noise, so a replay shakes the same way, and it never touches the node's transform or the game's random numbers.
@@ -0,0 +1,34 @@
package demo
import (
"aego/camera"
"aego/physics"
)
// Drop lifts the crate every few seconds; each landing shakes the camera.
script Drop {
@export var Every float = 2
@export(min=0, max=1)
var Hit float = 0.9
var t float
var start Vec2
func Ready() {
start = node.Transform2D().Position
}
func Update(dt float) {
t += dt
if t >= Every {
t = 0
body := node.Get<Body2D>()
body.Teleport(start, 0)
body.SetVelocity(Vec2{0, 0})
}
}
func OnCollisionEnter(other NodeRef, c physics.Contact) {
camera.Shake(Hit)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,27 @@
package demo
// Director raises Close, then Tilt, for Hold seconds each; the view blends between them.
script Director {
@export var Close ComponentRef<VirtualCamera2D>
@export var Tilt ComponentRef<VirtualCamera2D>
@export var Hold float = 2
var t float
var shot int
func Update(dt float) {
t += dt
if t < Hold {
return
}
t = 0
shot = (shot + 1) % 4
Close.Priority = -1
Tilt.Priority = -1
if shot == 1 {
Close.Priority = 1
} else if shot == 3 {
Tilt.Priority = 1
}
}
}
@@ -1,14 +1,14 @@
scene version=1 {
node "d5200000000000000000000000000001" name="Close" enabled=true layers=1 {
transform {
position=[6, 0]
rotation=0.08
position=[-6, 0]
rotation=0
scale=[1, 1]
}
component "camera.VirtualCamera2D" version=1 {
priority=-1
output=""
zoom=96
zoom=110
blendTime=1
blendCurve={ ease=inOutQuad }
timeDomain=unscaled
@@ -17,6 +17,24 @@ scene version=1 {
snapToPixels=false
}
}
node "d5200000000000000000000000000009" name="Tilt" enabled=true layers=1 {
transform {
position=[6, -0.4]
rotation=0.14
scale=[1, 1]
}
component "camera.VirtualCamera2D" version=1 {
priority=-1
output=""
zoom=80
blendTime=1.5
blendCurve={ ease=outBack }
timeDomain=unscaled
useBounds=false
bounds=[0, 0, 0, 0]
snapToPixels=false
}
}
node "d5200000000000000000000000000002" name="Camera" enabled=true layers=1 {
transform {
position=[0, 0]
@@ -160,4 +178,18 @@ scene version=1 {
filter=nearest
}
}
node "d5200000000000000000000000000010" name="Director" enabled=true layers=1 {
transform {
position=[0, 0]
rotation=0
scale=[1, 1]
}
component "engine.Script" version=1 {
script "game.demo.Director" enabled=true {
Close=node("d5200000000000000000000000000001")
Tilt=node("d5200000000000000000000000000009")
Hold=2
}
}
}
}
@@ -0,0 +1,52 @@
prefab version=1 {
node "d5600000000000000000000000000101" name="Lamp" enabled=true layers=1 {
transform {
position=[0, 0]
rotation=0
scale=[1, 1]
}
component "engine.Script" version=1 {
script "game.demo.Spin" enabled=true {
Speed=2
}
}
node "d5600000000000000000000000000102" name="Post" enabled=true layers=1 {
transform {
position=[0, 1.2]
rotation=0
scale=[1, 1]
}
component "engine.SpriteRenderer" version=1 {
sprite=asset("ae900000000000000000000000000010")
layer=0
order=0
tint=#6b7280ff
origin=[0.5, 0.5]
size=[0.3, 2.4]
flipX=false
flipY=false
blend=premultiplied
filter=nearest
}
}
node "d5600000000000000000000000000103" name="Head" enabled=true layers=1 {
transform {
position=[0, 0]
rotation=0
scale=[1, 1]
}
component "engine.SpriteRenderer" version=1 {
sprite=asset("ae900000000000000000000000000010")
layer=0
order=0
tint=#facc15ff
origin=[0.5, 0.5]
size=[1.2, 1.2]
flipX=false
flipY=false
blend=premultiplied
filter=nearest
}
}
}
}
@@ -0,0 +1,6 @@
meta version=1 {
id="d5600000000000000000000000000f01"
importer="prefab"
importerVersion=2
settings {}
}
@@ -0,0 +1,14 @@
package demo
// Spin turns the Head child of a lamp; each instance overrides Speed.
script Spin {
@export(unit="rad/s")
var Speed float = 2
func Update(dt float) {
head := node.Child("Head")
tr := head.Transform2D()
tr.Rotation += Speed * dt
head.SetTransform2D(tr)
}
}
@@ -0,0 +1,65 @@
scene version=1 {
node "d5600000000000000000000000000001" prefab=asset("d5600000000000000000000000000f01") {
id "d5600000000000000000000000000101" node=node("d5600000000000000000000000000001") {}
override "d5600000000000000000000000000101" prop="node.name" value="Red" {}
override "d5600000000000000000000000000101" prop="transform.position" value=[-2, 0] {}
override "d5600000000000000000000000000103" prop="engine.SpriteRenderer.tint" value=#ef4444ff {}
override "d5600000000000000000000000000101" prop="script/game.demo.Spin.Speed" value=-3 {}
}
node "d5600000000000000000000000000002" prefab=asset("d5600000000000000000000000000f01") {
id "d5600000000000000000000000000101" node=node("d5600000000000000000000000000002") {}
override "d5600000000000000000000000000101" prop="node.name" value="Lamp" {}
override "d5600000000000000000000000000101" prop="transform.position" value=[-6, 0] {}
}
node "d5600000000000000000000000000003" prefab=asset("d5600000000000000000000000000f01") {
id "d5600000000000000000000000000101" node=node("d5600000000000000000000000000003") {}
override "d5600000000000000000000000000101" prop="node.name" value="Big" {}
override "d5600000000000000000000000000101" prop="transform.position" value=[2, -0.6] {}
override "d5600000000000000000000000000101" prop="transform.scale" value=[1.5, 1.5] {}
override "d5600000000000000000000000000101" prop="script/game.demo.Spin.Speed" value=0.8 {}
}
node "d5600000000000000000000000000004" prefab=asset("d5600000000000000000000000000f01") {
id "d5600000000000000000000000000101" node=node("d5600000000000000000000000000004") {}
override "d5600000000000000000000000000101" prop="node.name" value="Blue" {}
override "d5600000000000000000000000000101" prop="transform.position" value=[6, 0] {}
override "d5600000000000000000000000000103" prop="engine.SpriteRenderer.tint" value=#3b82f6ff {}
override "d5600000000000000000000000000101" prop="script/game.demo.Spin.Speed" value=5 {}
}
node "d5600000000000000000000000000010" name="Floor" enabled=true layers=1 {
transform {
position=[0, 2.6]
rotation=0
scale=[1, 1]
}
component "engine.SpriteRenderer" version=1 {
sprite=asset("ae900000000000000000000000000010")
layer=0
order=0
tint=#374151ff
origin=[0.5, 0.5]
size=[18, 0.4]
flipX=false
flipY=false
blend=premultiplied
filter=nearest
}
}
node "d5600000000000000000000000000011" name="Camera" enabled=true layers=1 {
transform {
position=[0, 0]
rotation=0
scale=[1, 1]
}
component "engine.Camera2D" version=1 {
zoom=40
viewport=[0, 0, 1, 1]
mask=-1
order=0
clear=true
clearColor=#111827ff
scale=fitVirtual
virtual=[960, 540]
active=true
}
}
}
+4
View File
@@ -1,5 +1,7 @@
---
id: save
try: try/Persist.aescene
try_select: save.Persist
icon: panel.saves
group: gameplay
title: Saving games
@@ -9,6 +11,8 @@ order: 6
A save holds what you mark as persistent, not the whole scene. Loading a save reloads its scene from the project and then applies the save on top.
In Try it, the Checkpoint script saves after one second and then loads that save every few seconds. The green ball has Persist and comes back to where it was saved; the grey one has not and starts over from the scene file. In Try it saves stay in memory and are gone when the page closes.
| Mark | What is saved |
|---|---|
| `save.Persist` component | the node's transform and enabled flag |
+26
View File
@@ -0,0 +1,26 @@
package demo
import "aego/save"
// Checkpoint saves once, then keeps loading that save: the green ball has Persist and comes back
// to where it was saved, the grey one has not and starts over from the scene file.
script Checkpoint {
@export var SaveAt float = 1
@export var LoadEvery float = 2.5
var t float
func Update(dt float) {
t += dt
if !save.Exists("demo") {
if t >= SaveAt {
save.Save("demo")
t = 0
}
return
}
if t >= LoadEvery {
save.Load("demo")
}
}
}
File diff suppressed because it is too large Load Diff
+26
View File
@@ -0,0 +1,26 @@
package demo
import "aego/math"
// Spin turns its node and bobs it up and down; edit the fields in the Inspector.
script Spin {
@export(unit="rad/s")
var Speed float = 2
@export var Bob float = 1
@export var Rate float = 3
var t float
var base Vec2
func Ready() {
base = node.Transform2D().Position
}
func Update(dt float) {
t += dt
tr := node.Transform2D()
tr.Rotation += Speed * dt
tr.Position = base + Vec2{0, -math.Abs(math.Sin(t * Rate)) * Bob}
node.SetTransform2D(tr)
}
}
@@ -0,0 +1,72 @@
scene version=1 {
node "d5500000000000000000000000000001" name="Spinner" enabled=true layers=1 {
transform {
position=[0, 0]
rotation=0
scale=[1, 1]
}
component "engine.Script" version=1 {
script "game.demo.Spin" enabled=true {
Speed=2
Bob=1
Rate=3
}
}
node "d5500000000000000000000000000004" name="Body" enabled=true layers=1 {
transform {
position=[0, 0]
rotation=0
scale=[1, 1]
}
component "engine.SpriteRenderer" version=1 {
sprite=asset("ae900000000000000000000000000010")
layer=0
order=0
tint=#22c55eff
origin=[0.5, 0.5]
size=[2, 2]
flipX=false
flipY=false
blend=premultiplied
filter=nearest
}
}
}
node "d5500000000000000000000000000002" name="Floor" enabled=true layers=1 {
transform {
position=[0, 2.6]
rotation=0
scale=[1, 1]
}
component "engine.SpriteRenderer" version=1 {
sprite=asset("ae900000000000000000000000000010")
layer=0
order=0
tint=#374151ff
origin=[0.5, 0.5]
size=[12, 0.4]
flipX=false
flipY=false
blend=premultiplied
filter=nearest
}
}
node "d5500000000000000000000000000003" name="Camera" enabled=true layers=1 {
transform {
position=[0, 0]
rotation=0
scale=[1, 1]
}
component "engine.Camera2D" version=1 {
zoom=40
viewport=[0, 0, 1, 1]
mask=-1
order=0
clear=true
clearColor=#111827ff
scale=fitVirtual
virtual=[960, 540]
active=true
}
}
}
+49
View File
@@ -0,0 +1,49 @@
package demo
import (
"aego/math"
"aego/scene"
)
// Spotlight pulses every enabled node with Tag; it cycles through three tags every few seconds.
script Spotlight {
@export var Tag string = "Enemy"
@export var Every float = 1.5
var found []NodeRef
var tags []string
var t float
var pick int
func Ready() {
for i := 0; i < 16; i++ {
found = append(found, nil)
}
tags = append(tags, Tag)
tags = append(tags, "Enemy")
tags = append(tags, "Pickup")
}
func Update(dt float) {
t += dt
if t >= Every {
t = 0
pick = (pick + 1) % len(tags)
}
for k := 0; k < len(tags); k++ {
s := 1.0
if k == pick {
s = 1 + 0.4 * math.Abs(math.Sin(t * 6))
}
n := scene.WithTag(tags[k], found)
for i := 0; i < n; i++ {
if k != pick && found[i].HasTag(tags[pick]) {
continue
}
tr := found[i].Transform2D()
tr.Scale = Vec2{s, s}
found[i].SetTransform2D(tr)
}
}
}
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -186,7 +186,7 @@ var wasmDenied = []string{
var wasmExtra = []string{
"modules/render2d", "modules/scripting/...", "modules/input", "modules/physics/...", "modules/debug", "modules/tween", "modules/camera", "modules/render3d",
"app/assembly", "app/assembly/playground", "app/playground", "cmd/aego-playground",
"modules/spawn", "modules/savegame", "app/assembly", "app/assembly/playground", "app/playground", "cmd/aego-playground",
}
// match reports whether p matches pattern: "a/b", "a/...", "..." or "*" per segment.
+43 -3
View File
@@ -38,6 +38,14 @@ var tokenClass = [highlight.KindCount]string{
highlight.Bracket: "br",
}
// lightTokens is the code palette for the docs' light theme; the dark one is the editor's.
var lightTokens = map[string]string{
"kw": "#a626a4", "num": "#986801", "str": "#3f7f3a", "com": "#80838c", "op": "#4b4f58", "pun": "#6a6f7a",
"attr": "#946200", "ns": "#b4510f", "ty": "#0b7285", "bty": "#0b7285", "fn": "#2459c7", "me": "#2459c7",
"par": "#a3530a", "var": "#383a42", "fld": "#c0392b", "prop": "#c0392b", "con": "#986801", "en": "#986801",
"bi": "#b0257a", "scr": "#0b7285", "err": "#d1242f", "sig": "#b4510f", "co": "#0f766e", "br": "#6a6f7a",
}
func highlightAES(src string) string {
return highlightLinked(src, nil)
}
@@ -110,17 +118,49 @@ func hex(c mathx.Color) string {
return fmt.Sprintf("#%02x%02x%02x", to(c.R), to(c.G), to(c.B))
}
func pageBackground() string {
return hex(theme.Editor().Background)
}
func cssColor(c mathx.Color) string {
if c.A >= 1 {
return hex(c)
}
h := hex(c)
return fmt.Sprintf("rgb(%s %s %s / %.2f)", fmt.Sprint(hexByte(h[1:3])), fmt.Sprint(hexByte(h[3:5])), fmt.Sprint(hexByte(h[5:7])), c.A)
}
func hexByte(s string) int {
var v int
fmt.Sscanf(s, "%02x", &v)
return v
}
func codeCSS() string {
t := theme.Code()
pal := highlight.Palette(&t)
var b strings.Builder
b.WriteString("/* generated from the editor's code theme (ui/theme.Code) */\n")
b.WriteString(":root {\n --code-bg: " + hex(t.Background) + ";\n --code-gutter: " + hex(t.GutterBg) + ";\n --code-text: " + hex(t.Plain) + ";\n --code-line: " + hex(t.LineNo) + ";\n --code-error: " + hex(t.Error) + ";\n}\n")
b.WriteString("/* generated from the editor's themes (ui/theme.Code and ui/theme.Editor) */\n")
ld := func(light, dark string) string { return "light-dark(" + light + ", " + dark + ")" }
b.WriteString(":root {\n --code-bg: " + ld("#f5f6f8", hex(t.Background)) + ";\n --code-gutter: " + ld("#eceef1", hex(t.GutterBg)) + ";\n --code-text: " + ld("#383a42", hex(t.Plain)) + ";\n --code-line: " + ld("#9aa0a9", hex(t.LineNo)) + ";\n --code-error: " + ld("#d1242f", hex(t.Error)) + ";\n")
e := theme.Editor()
for _, v := range []struct {
name string
c mathx.Color
}{
{"bg", e.Background}, {"header", e.Header}, {"panel", e.Panel}, {"panel-alt", e.PanelAlt}, {"hover", e.TabHot}, {"widget", e.Widget},
{"border", e.Border}, {"separator", e.Separator}, {"text", e.Text}, {"text-dim", e.TextDim}, {"unit", e.Unit},
{"accent", e.Accent}, {"error", e.Error}, {"warn", e.Warn}, {"ok", e.Ok},
{"axis-x", e.AxisX}, {"axis-y", e.AxisY}, {"axis-z", e.AxisZ},
} {
b.WriteString(" --ed-" + v.name + ": " + cssColor(v.c) + ";\n")
}
b.WriteString("}\n")
for k, cls := range tokenClass {
if cls == "" {
continue
}
b.WriteString(".t-" + cls + " { color: " + hex(pal[k]) + "; }\n")
b.WriteString(".t-" + cls + " { color: " + ld(lightTokens[cls], hex(pal[k])) + "; }\n")
}
b.WriteString(".t-com { font-style: italic; }\n")
return b.String()
+14
View File
@@ -85,10 +85,24 @@ func (b *builder) icon(root, name, cls string) string {
return `<svg width="16" height="16" class="` + cls + `" aria-hidden="true"><use href="` + root + `assets/icons.svg?v=` + b.assetV + `#` + name + `"/></svg>`
}
// refIcons gives script packages and project settings sections the editor icon of what they cover.
var refIcons = map[string]string{
"aego/builtin": "cat.node", "events": "panel.events", "aego/action": "panel.tasks", "aego/audio": "cat.audio",
"aego/camera": "cat.camera", "aego/curve": "panel.profiler", "aego/data": "file.data", "aego/debug": "panel.debug_draw",
"aego/input": "panel.input_devices", "aego/log": "panel.console", "aego/math": "cat.transform", "aego/physics": "cat.physics",
"aego/rand": "shape.cube", "aego/save": "panel.saves", "aego/scene": "file.scene", "aego/settings": "cat.settings",
"aego/testing": "status.ok", "aego/time": "play.speed", "aego/timer": "file.autosave", "aego/tween": "vec.velocity",
"section/engine": "panel.settings", "section/input": "panel.input_devices", "section/render2d": "cat.rendering",
"section/physics": "cat.physics", "section/debug": "panel.debug_draw", "section/player": "play.start", "section/audio": "cat.audio",
}
func (b *builder) entryIcon(e *Entry) string {
if e.Icon != "" {
return e.Icon
}
if ic, ok := refIcons[e.ID]; ok {
return ic
}
if e.Kind == KindField && e.Field != nil {
return fieldIcon(e.Title, e.Field.Type)
}
+3
View File
@@ -64,6 +64,9 @@ type Entry struct {
Related []string
Try string
TrySelect string
TryScript string
TryCode string
TryPrefabs map[string]string
Icon string
EditorOnly bool
Diag *DiagInfo
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -101,7 +101,7 @@ func crawl(t *testing.T, dir string) map[string]string {
file = path.Join(file, "index.html")
}
}
if strings.HasSuffix(file, ".css") || strings.HasSuffix(file, ".js") {
if strings.HasSuffix(file, ".css") || strings.HasSuffix(file, ".js") || strings.HasSuffix(file, ".woff2") {
if _, err := os.Stat(filepath.Join(dir, file)); err != nil {
t.Errorf("%s: missing asset %s", p, href)
}
@@ -203,7 +203,7 @@ func TestShell(t *testing.T) {
`Aego 1.2.3`,
`<a href="#install">Install</a>`,
`class="next" href="language.html"`,
`data-group="start" open data-current="true"`,
`class="item head current" aria-current="page"`,
`aria-current="page"`,
} {
if !strings.Contains(w, want) {
@@ -211,7 +211,7 @@ func TestShell(t *testing.T) {
}
}
co := pages["language/coroutines.html"]
if !strings.Contains(co, `<nav class="crumbs"><span>Guides</span><span class="sep">/</span><span>Aego Script</span><span class="sep">/</span><a href="../language.html">Aego Script</a></nav>`) {
if !strings.Contains(co, `<nav class="crumbs"><span>Guides</span><span class="sep">/</span><a href="../language.html">Aego Script</a></nav>`) {
t.Error("breadcrumbs are wrong on the coroutines page")
}
if !strings.Contains(pages["index.html"], `class="button primary" href="welcome.html"`) {
@@ -0,0 +1,93 @@
Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
+39 -9
View File
@@ -13,7 +13,40 @@ const assetV = (document.querySelector('meta[name="aego-assets"]') || {}).conten
let active = 0;
let shown = [];
document.querySelector(".menu").addEventListener("click", () => document.body.classList.toggle("nav-open"));
const menu = document.querySelector(".menu");
const scrim = document.querySelector(".scrim");
const doc = document.querySelector(".doc");
const narrow = matchMedia("(max-width: 899px)");
function setNav(open) {
document.body.classList.toggle("nav-open", open);
menu.setAttribute("aria-expanded", String(open));
scrim.hidden = !open;
doc.inert = open;
document.body.style.overflow = open ? "hidden" : "";
}
menu.addEventListener("click", () => setNav(!document.body.classList.contains("nav-open")));
scrim.addEventListener("click", () => setNav(false));
document.querySelector(".side").addEventListener("click", (e) => {
if (e.target.closest("a") && narrow.matches) setNav(false);
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && document.body.classList.contains("nav-open")) setNav(false);
});
narrow.addEventListener("change", (e) => {
if (!e.matches) setNav(false);
});
const THEME_KEY = "aego.docs.theme";
const prefersLight = matchMedia("(prefers-color-scheme: light)");
document.querySelector(".theme").addEventListener("click", () => {
const cur = document.documentElement.dataset.theme || (prefersLight.matches ? "light" : "dark");
const next = cur === "light" ? "dark" : "light";
document.documentElement.dataset.theme = next;
try {
localStorage.setItem(THEME_KEY, next);
} catch (_) {}
});
function loadState() {
try {
@@ -26,9 +59,6 @@ const assetV = (document.querySelector('meta[name="aego-assets"]') || {}).conten
const state = loadState();
for (const d of document.querySelectorAll(".side details.group")) {
const id = d.dataset.group;
if (d.dataset.current) d.open = true;
else if (id in state) d.open = !!state[id];
else d.open = id === "start";
d.addEventListener("toggle", () => {
state[id] = d.open;
try {
@@ -37,11 +67,11 @@ const assetV = (document.querySelector('meta[name="aego-assets"]') || {}).conten
});
}
const side = document.querySelector(".side");
const current = document.querySelector(".side a.current");
if (side && current) {
const top = current.getBoundingClientRect().top - side.getBoundingClientRect().top + side.scrollTop;
side.scrollTop = Math.max(0, top - side.clientHeight / 2);
}
addEventListener("pagehide", () => {
try {
sessionStorage.setItem("aego.docs.side", String(side.scrollTop));
} catch (_) {}
});
for (const pre of document.querySelectorAll(".doc pre")) {
const code = pre.querySelector("code");
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -112,7 +112,7 @@
api = await boot(p.update);
p.box.remove();
if (active !== b) return;
const res = call("load", { scene: b.dataset.scene, select: b.dataset.select || "" });
const res = call("load", { scene: b.dataset.scene, select: b.dataset.select || "", code: b.dataset.code || "", prefabs: b.dataset.prefabs ? JSON.parse(b.dataset.prefabs) : {} });
if (res.error) throw new Error(res.error);
show(b, res.text, res.values);
b.querySelector(".try-reset").disabled = false;
+21 -10
View File
@@ -8,31 +8,42 @@
{{if .Playground}}<meta name="aego-play" content="{{.PlayVersion}}"><meta name="aego-try" content="{{.TryVersion}}"><meta name="aego-play-size" content="{{.PlaySize}}"><meta name="aego-try-size" content="{{.TrySize}}">{{end}}
<title>{{.Title}} · {{.Site}}</title>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="{{.Root}}assets/style.css?v={{.AssetV}}">
<link rel="stylesheet" href="{{.Root}}assets/code.css?v={{.AssetV}}">
<meta name="color-scheme" content="dark light">
<script>try { const t = localStorage.getItem("aego.docs.theme"); if (t) document.documentElement.dataset.theme = t; } catch (_) {}</script>
<style>html { color-scheme: dark light; background: {{.Background}}; } html[data-theme="light"] { color-scheme: light; background: #fbfaf8; } html[data-theme="dark"] { color-scheme: dark; } @media (prefers-color-scheme: light) { html:not([data-theme="dark"]) { background: #fbfaf8; } }</style>
<link rel="preload" href="{{.Root}}assets/fonts/ibm-plex-sans-400.woff2" as="font" type="font/woff2" crossorigin>
<style>{{.CSS}}</style>
</head>
<body{{if .Home}} class="home"{{end}}>
<header class="top">
<button type="button" class="menu" aria-label="Toggle navigation"><span></span><span></span><span></span></button>
<a class="brand" href="{{.Root}}index.html"><span>{{.Site}}</span></a>{{if .Rev}}<a class="rev" href="{{.RevURL}}" title="Documentation revision">{{.Rev}}</a>{{end}}
<div class="top-inner">
<button type="button" class="menu" aria-label="Navigation" aria-expanded="false" aria-controls="side"><span></span><span></span><span></span></button>
<a class="brand" href="{{.Root}}index.html">{{.Site}}</a>
<div class="search">
{{icon .Root "ui.search"}}
<input id="search" type="search" placeholder="Search the docs" autocomplete="off" aria-label="Search">
<kbd>/</kbd>
<ul id="results" hidden></ul>
</div>
<nav class="links">
<a href="{{.Root}}index.html">{{icon .Root "help.book"}}<span>Docs</span></a>
{{if .Playground}}<a href="{{.Root}}play/">{{icon .Root "file.script"}}<span>Playground</span></a>{{end}}
{{if .Demo}}<a href="{{.Root}}demo/">{{icon .Root "panel.game"}}<span>Demo</span></a>{{end}}
{{if .Repo}}<a class="repo" href="{{.Repo}}" title="Source code" aria-label="Source code"><svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M23.546 10.93 13.067.452a1.55 1.55 0 0 0-2.188 0L8.708 2.627l2.76 2.76a1.838 1.838 0 0 1 2.327 2.341l2.658 2.66a1.838 1.838 0 1 1-1.103 1.033l-2.48-2.48v6.525a1.838 1.838 0 1 1-1.512-.054V8.826a1.838 1.838 0 0 1-.998-2.41L7.636 3.7.45 10.881a1.55 1.55 0 0 0 0 2.189l10.48 10.477a1.55 1.55 0 0 0 2.186 0l10.43-10.43a1.55 1.55 0 0 0 0-2.187"/></svg></a>{{end}}
<nav class="links" aria-label="Site">
<a href="{{.Root}}index.html">Docs</a>
{{if .Playground}}<a href="{{.Root}}play/">Playground</a>{{end}}
{{if .Demo}}<a href="{{.Root}}demo/">Demo</a>{{end}}
{{if .Repo}}<a class="repo" href="{{.Repo}}" title="Source code" aria-label="Source code"><svg viewBox="0 0 24 24" width="17" height="17" aria-hidden="true"><path fill="currentColor" d="M23.546 10.93 13.067.452a1.55 1.55 0 0 0-2.188 0L8.708 2.627l2.76 2.76a1.838 1.838 0 0 1 2.327 2.341l2.658 2.66a1.838 1.838 0 1 1-1.103 1.033l-2.48-2.48v6.525a1.838 1.838 0 1 1-1.512-.054V8.826a1.838 1.838 0 0 1-.998-2.41L7.636 3.7.45 10.881a1.55 1.55 0 0 0 0 2.189l10.48 10.477a1.55 1.55 0 0 0 2.186 0l10.43-10.43a1.55 1.55 0 0 0 0-2.187"/></svg></a>{{end}}
</nav>
<button type="button" class="theme" aria-label="Switch theme" title="Switch theme"><svg class="sun" viewBox="0 0 24 24" width="17" height="17" aria-hidden="true"><circle cx="12" cy="12" r="4.2" fill="currentColor"/><path stroke="currentColor" stroke-width="1.8" stroke-linecap="round" d="M12 2.5v2.2M12 19.3v2.2M2.5 12h2.2M19.3 12h2.2M5.3 5.3l1.6 1.6M17.1 17.1l1.6 1.6M5.3 18.7l1.6-1.6M17.1 6.9l1.6-1.6"/></svg><svg class="moon" viewBox="0 0 24 24" width="17" height="17" aria-hidden="true"><path fill="currentColor" d="M20.6 14.6A8.6 8.6 0 0 1 9.4 3.4a8.6 8.6 0 1 0 11.2 11.2Z"/></svg></button>
</div>
</header>
<div class="shell">
<nav class="side" aria-label="Documentation">{{.Nav}}</nav>
<nav class="side" id="side" aria-label="Documentation">
<div class="side-links"><a href="{{.Root}}index.html">Docs</a>{{if .Playground}}<a href="{{.Root}}play/">Playground</a>{{end}}{{if .Demo}}<a href="{{.Root}}demo/">Demo</a>{{end}}{{if .Repo}}<a href="{{.Repo}}">Source</a>{{end}}</div>
{{.Nav}}
</nav>
<script>(function () { const side = document.getElementById("side"); try { const st = JSON.parse(localStorage.getItem("aego.docs.groups")) || {}; for (const d of side.querySelectorAll("details.group")) if (!d.dataset.current && d.dataset.group in st) d.open = !!st[d.dataset.group]; } catch (_) {} let y = null; try { y = sessionStorage.getItem("aego.docs.side"); } catch (_) {} if (y !== null) side.scrollTop = +y; const cur = side.querySelector("a.current"); if (cur) { const top = cur.offsetTop - side.offsetTop; if (top < side.scrollTop || top > side.scrollTop + side.clientHeight - 40) side.scrollTop = Math.max(0, top - side.clientHeight / 2); } })();</script>
<main class="doc">{{.Content}}{{.Pager}}</main>
<aside class="toc">{{.Toc}}</aside>
</div>
<div class="scrim" hidden></div>
{{.Footer}}
<script src="{{.Root}}assets/site.js?v={{.AssetV}}"></script>
{{if .Playground}}<script src="{{.Root}}assets/loader.js?v={{.AssetV}}"></script><script src="{{.Root}}assets/run.js?v={{.AssetV}}"></script><script src="{{.Root}}assets/try.js?v={{.AssetV}}"></script>{{end}}

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