90 Commits
Author SHA1 Message Date
sanspie df8c926338 Web export: one sound button; the site demo starts silent and Try it starts with sound, each remembering its own choice; drop the attack hint the demo does not have
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 4s
ci / checks (push) Failing after 4s
ci / release (push) Skipped
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
2026-10-22 13:09:30 +03:00
sanspie 195703820b Stage 10d1: add-component requirements, play diff from history, node labels 2026-10-22 13:09:11 +03:00
sanspie 73d5fe6c88 Web export: a music button that persists the player's choice; the site demo starts with music off; the track resolves into its loop start
ci / checks (push) Successful in 13m53s
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) Failing after 1s
ci / release (push) Skipped
2026-10-22 12:16:37 +03:00
sanspie 76bc68606d Music: drop the crash at the start of each phrase
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / checks (push) Failing after 0s
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 0s
ci / release (push) Skipped
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
2026-10-22 12:06:18 +03:00
sanspie 54693f97d2 Music: metallic hi-hats and a crash per phrase instead of noise ticks; encode the track at vorbis q3
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 1s
ci / release (push) Skipped
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 0s
2026-10-22 11:58:43 +03:00
sanspie 080948f434 Stage 10c3: platformer and top-down examples on the gameplay kit 2026-10-22 11:58:38 +03:00
sanspie e957ffb268 Music: new platformer track and built-in Melody (pad, plucked arpeggio, bass, drums, lead, delay and reverb; seamless loops)
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) Failing after 0s
ci / checks (push) Successful in 14m36s
ci / release (push) Skipped
2026-10-22 11:54:58 +03:00
sanspie c24a4791fa Stage 10c2b: StateMachine and script calls, respawn persistence, gameplay editor tools (D332)
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci / checks (push) Failing after 4s
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 3s
ci / release (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
2026-10-22 10:53:54 +03:00
sanspie 12e9f5d0d0 Short component names prefer the project's own component over engine modules (D93) 2026-10-22 06:44:09 +03:00
sanspie 24284632d5 Stage 10c2a: health, hitboxes, zones, respawn, pickups, lifetime, templates (D313, D330) 2026-10-22 06:19:20 +03:00
sanspie 60556df87c Stage 10c1: shape casts and Motion2D (D312, D328, D329)
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / checks (push) Failing after 0s
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 1s
ci / release (push) Skipped
2026-10-22 03:01:46 +03:00
sanspie 67586b178c Camera: a blend started during a rewind replay starts from the previous camera's pose (D306) 2026-10-22 03:01:46 +03:00
sanspie 2f8ea7af98 Asset grid: audio cards, folder previews, audition in pickers; Add Component adds required components 2026-10-22 03:01:46 +03:00
sanspie c4643dc20c web export: editor icons for the mute and fullscreen buttons
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 0s
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / checks (push) Failing after 0s
ci / release (push) Skipped
2026-10-22 02:38:14 +03:00
sanspie c629199eb5 web audio: estimate the worklet queue by the audio clock and keep 6 blocks without SharedArrayBuffer
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) Skipped
ci / checks (push) Failing after 1s
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 0s
2026-10-22 02:26:41 +03:00
sanspie 80e8966fb0 docs: download card with a download icon, files grouped by OS
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) Canceled after 0s
ci / build (amd64, linux, ubuntu-24.04) (push) Canceled after 1h39m8s
ci / checks (push) Successful in 1h16m34s
2026-10-22 01:59:16 +03:00
sanspie 35f58cdd23 docs: sound button in Try it for scenes that play audio
ci / release (push) Skipped
ci / checks (push) Successful in 1h57m35s
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) Failing after 3h11m4s
2026-10-22 01:40:19 +03:00
sanspie 8391ebcd98 docs: plan the end of Stage 11
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 29m44s
ci / checks (push) Successful in 15m25s
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 20m0s
2026-10-21 22:27:08 +03:00
sanspie 3ab43e84f1 Windows start: boot log, fatal message box, OpenGL driver error 2026-10-21 22:27:04 +03:00
sanspie 62b5f640fc edge: clean URLs, .html redirects and 60 s page cache on the edge proxy
ci / checks (push) Successful in 35m9s
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) Failing after 1h23m6s
ci / release (push) Skipped
2026-10-21 21:13:55 +03:00
sanspie 814cd2bf07 docs: clean URLs without .html, old .html links redirect; keep != two characters in code
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci / checks (push) Successful in 20m39s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 38m26s
ci / release (push) Successful in 20m31s
2026-10-21 20:54:44 +03:00
sanspie 8e717a4fd1 Stage 10b3–10b3b: desktop onboarding, app icon, clip inspector (D327)
ci / checks (push) Successful in 31m3s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 48m31s
ci / release (push) Successful in 1h28m27s
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
2026-10-21 20:33:32 +03:00
sanspie a2c3a24381 Stage 10b: audio mixer, codecs, clips, events, sources, zones and mixer UI (D307–D311, D321–D326) 2026-10-21 20:33:25 +03:00
sanspie 9da9851538 docs: description and Open Graph title on every page and the playground
ci / checks (push) Successful in 14m32s
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 17m14s
ci / release (push) Successful in 19m26s
2026-10-21 19:20:52 +03:00
sanspie 4fd1e11741 docs: download buttons for the visitor's OS from the latest release
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 15m54s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 17m37s
ci / release (push) Successful in 19m37s
2026-10-21 19:05:12 +03:00
sanspie 8f9a0299dc depcheck: finish the wasm fixture rename to modules/accessibility
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 19m28s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 17m22s
ci / release (push) Successful in 20m16s
2026-10-21 18:36:14 +03:00
sanspie ca3eedf9b0 docs: tell Dark Reader the site has its own dark theme
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 13m37s
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) Skipped
ci / checks (push) Failing after 12m24s
2026-10-21 17:17:03 +03:00
sanspie 4f0662eb3b docs: highlighted inline code looks like other inline code and wraps cleanly
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 5m55s
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 10m29s
ci / release (push) Skipped
2026-10-21 17:12:02 +03:00
sanspie 9abf5c75ab playground: hide the game view for console examples
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci / checks (push) Failing after 10m20s
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci / release (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 12m23s
2026-10-21 17:07:27 +03:00
sanspie 96fef1f6a8 docs: real-export nav golden without the unreleased audio module 2026-10-21 17:06:11 +03:00
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
195 changed files with 171 additions and 2701 deletions
+23 -73
View File
File diff suppressed because it is too large Load Diff
+4 -35
View File
@@ -40,14 +40,14 @@ BENCH_PHYSICS_PKGS :=
.DEFAULT_GOAL := bootstrap .DEFAULT_GOAL := bootstrap
-include modules/*/module.mk -include modules/*/module.mk
.PHONY: dmg nsis-check appimage 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 affected quick check-camera check-audio check-gameplay check-mutate check-editor check-web check-docs pkgsize .PHONY: dmg nsis-check appimage 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
XVFB := $(shell command -v xvfb-run 2>/dev/null) XVFB := $(shell command -v xvfb-run 2>/dev/null)
GL_OVERRIDE = MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330 GL_OVERRIDE = MESA_GL_VERSION_OVERRIDE=3.3 MESA_GLSL_VERSION_OVERRIDE=330
ifneq ($(XVFB),) ifneq ($(XVFB),)
GL_ENV = $(XVFB) -a -s "-screen 0 1280x720x24" env SDL_VIDEODRIVER=x11 SDL_AUDIO_DRIVER=dummy LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe $(GL_OVERRIDE) GL_ENV = $(XVFB) -a -s "-screen 0 1280x720x24" env SDL_VIDEODRIVER=x11 LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe $(GL_OVERRIDE)
else else
GL_ENV = env SDL_VIDEODRIVER=offscreen SDL_AUDIO_DRIVER=dummy LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe $(GL_OVERRIDE) GL_ENV = env SDL_VIDEODRIVER=offscreen LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe $(GL_OVERRIDE)
endif endif
NONATIVE_PKGS = $$(go list ./... | grep -v -e '/platform/sdl$$' -e '/gpu/gl33$$' -e '/gpu/gl33/gl$$') NONATIVE_PKGS = $$(go list ./... | grep -v -e '/platform/sdl$$' -e '/gpu/gl33$$' -e '/gpu/gl33/gl$$')
@@ -247,7 +247,6 @@ try-size:
go run ./tools/wasmsize -top 15 -depth 2 .cache/aego-try.wasm go run ./tools/wasmsize -top 15 -depth 2 .cache/aego-try.wasm
web-smoke: site web-smoke: site
sh tools/webbudget.sh site
$(WEB_SMOKE) --report=$(CURDIR)/.cache/web-bench.json $(WEB_SMOKE) --report=$(CURDIR)/.cache/web-bench.json
$(WEB_DEMO) $(WEB_DEMO)
$(WEB_TRY) $(WEB_TRY)
@@ -256,7 +255,6 @@ web-smoke: site
web-smoke-update: site web-smoke-update: site
$(WEB_SMOKE) --update $(WEB_SMOKE) --update
$(WEB_DEMO) --update $(WEB_DEMO) --update
$(WEB_TRY) --update
cli: bootstrap cli: bootstrap
go build -o .cache/bin/aego ./cmd/aego go build -o .cache/bin/aego ./cmd/aego
@@ -292,42 +290,13 @@ fuzz:
go test -run '^$$' -fuzz '^FuzzWAV$$' -fuzztime $(FUZZTIME) ./audio/codec go test -run '^$$' -fuzz '^FuzzWAV$$' -fuzztime $(FUZZTIME) ./audio/codec
go test -run '^$$' -fuzz '^FuzzVorbis$$' -fuzztime $(FUZZTIME) ./audio/codec go test -run '^$$' -fuzz '^FuzzVorbis$$' -fuzztime $(FUZZTIME) ./audio/codec
affected:
@pkgs=$$(sh tools/affected.sh $(BASE)); if [ -z "$$pkgs" ]; then echo "nothing affected"; else go vet $$pkgs && go test $$pkgs; fi
quick:
@pkgs=$$(sh tools/affected.sh $(BASE)); if [ -n "$$pkgs" ]; then go build $$pkgs && go test -short $$pkgs; fi
check-camera:
go test ./modules/camera/... ./tests/camera/...
check-audio:
go test ./audio/... ./modules/audio/... ./tests/audio/...
check-gameplay:
go test ./modules/gameplay/... ./tests/gameplay/... ./modules/physics/...
check-mutate:
go test ./scene/mutate/... ./tests/mutate/... ./app/agent/...
check-editor:
go test ./editor/... ./app/edit/...
check-web: test-wasm web-smoke
check-docs: site-check
pkgsize:
@sh tools/pkgsize.sh
ci: build vet lint depcheck test test-nonative test-editor test-runtime test-wasm runtime smoke fuzz golden shots-check test-gl cli playground web-smoke site-check example example-platformer check-all export-smoke $(MODULE_CI) bench-allocs ci: build vet lint depcheck test test-nonative test-editor test-runtime test-wasm runtime smoke fuzz golden 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. # Documentation screenshots of the real editor on the platformer example, under a clean user profile.
SHOTS_TMP = $(CURDIR)/.cache/shots SHOTS_TMP = $(CURDIR)/.cache/shots
ifneq ($(XVFB),) ifneq ($(XVFB),)
SHOTS_ENV = $(XVFB) -a -s "-screen 0 1600x900x24" env SDL_VIDEODRIVER=x11 SDL_AUDIO_DRIVER=dummy LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe $(GL_OVERRIDE) SHOTS_ENV = $(XVFB) -a -s "-screen 0 1600x900x24" env SDL_VIDEODRIVER=x11 LIBGL_ALWAYS_SOFTWARE=1 GALLIUM_DRIVER=llvmpipe $(GL_OVERRIDE)
else else
SHOTS_ENV = $(GL_ENV) SHOTS_ENV = $(GL_ENV)
endif endif
-8
View File
@@ -1,8 +0,0 @@
# Aego
A 2D and 3D game engine with an editor, a scripting language and a web export, written in Go.
- Documentation and the playground: <https://aego.akarpov.ru>
- Installing and building from source: [engine/res/docs/install.md](engine/res/docs/install.md)
- Contributing, check levels and rules for engine code: [docs/contributing.md](docs/contributing.md)
- Architecture and decisions: [ARCHITECTURE.md](ARCHITECTURE.md)
-4
View File
@@ -2,7 +2,6 @@ package agent
import ( import (
"errors" "errors"
"maps"
"os" "os"
"path" "path"
"slices" "slices"
@@ -288,9 +287,6 @@ func (s *Session) sceneRun(a Args) (*Output, error) {
} }
var b strings.Builder var b strings.Builder
b.WriteString("frames: " + strconv.Itoa(rep.Frames) + "\nhash: " + rep.Hash + "\n") b.WriteString("frames: " + strconv.Itoa(rep.Frames) + "\nhash: " + rep.Hash + "\n")
for _, k := range slices.Sorted(maps.Keys(rep.Counters)) {
b.WriteString(k + ": " + strconv.FormatUint(rep.Counters[k], 10) + "\n")
}
for _, l := range rep.Log { for _, l := range rep.Log {
b.WriteString(l + "\n") b.WriteString(l + "\n")
} }
-55
View File
@@ -1,7 +1,6 @@
package agent package agent
import ( import (
"aego/script/abi"
"errors" "errors"
"strings" "strings"
@@ -35,14 +34,6 @@ type TypeView struct {
Requires []string `json:"requires,omitempty"` Requires []string `json:"requires,omitempty"`
Doc string `json:"doc,omitempty"` Doc string `json:"doc,omitempty"`
Fields []FieldView `json:"fields,omitempty"` Fields []FieldView `json:"fields,omitempty"`
Methods []CallView `json:"methods,omitempty"`
Signals []CallView `json:"signals,omitempty"`
}
type CallView struct {
Name string `json:"name"`
Sig string `json:"sig"`
Doc string `json:"doc,omitempty"`
} }
func schemaCommands() []*Command { func schemaCommands() []*Command {
@@ -172,51 +163,5 @@ func (s *Session) schemaShow(a Args) (*Output, error) {
} }
b.WriteString("\n") b.WriteString("\n")
} }
if s.kit != nil {
for _, cm := range s.kit.methods(td.FullName) {
v.Methods = append(v.Methods, CallView{Name: cm.Name, Sig: methodSig(cm.Sig, cm.Params), Doc: summary(cm.Doc)})
}
}
for _, sg := range td.Signals {
params := make([]string, len(sg.Params))
for i, p := range sg.Params {
params[i] = p
if i < len(sg.Names) && sg.Names[i] != "" {
params[i] = sg.Names[i] + " " + p
}
}
v.Signals = append(v.Signals, CallView{Name: sg.Name, Sig: "(" + strings.Join(params, ", ") + ")", Doc: summary(sg.Doc)})
}
for _, sec := range []struct {
title string
list []CallView
}{{"methods", v.Methods}, {"signals", v.Signals}} {
if len(sec.list) == 0 {
continue
}
b.WriteString(sec.title + ":\n")
for _, c := range sec.list {
b.WriteString(" " + c.Name + c.Sig)
if c.Doc != "" {
b.WriteString(" — " + c.Doc)
}
b.WriteString("\n")
}
}
return &Output{Value: v, Text: b.String()}, nil return &Output{Value: v, Text: b.String()}, nil
} }
func methodSig(sig abi.Signature, names []string) string {
params := make([]string, len(sig.Params))
for i, p := range sig.Params {
params[i] = p.String()
if i < len(names) && names[i] != "" {
params[i] = names[i] + " " + params[i]
}
}
out := "(" + strings.Join(params, ", ") + ")"
if sig.Result.Kind != abi.Void {
out += " " + sig.Result.String()
}
return out
}
-21
View File
@@ -28,7 +28,6 @@ import (
"aego/scene/sceneio" "aego/scene/sceneio"
"aego/schema" "aego/schema"
"aego/script/bind" "aego/script/bind"
"aego/script/bind/manifest"
"aego/script/reload" "aego/script/reload"
"aego/script/runtime" "aego/script/runtime"
"aego/script/workspace" "aego/script/workspace"
@@ -49,7 +48,6 @@ type RunRequest struct {
type RunReport struct { type RunReport struct {
Frames int `json:"frames"` Frames int `json:"frames"`
Hash string `json:"hash"` Hash string `json:"hash"`
Counters map[string]uint64 `json:"counters,omitempty"`
Log []string `json:"log,omitempty"` Log []string `json:"log,omitempty"`
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
} }
@@ -81,25 +79,6 @@ type Kit struct {
ws *workspace.Workspace ws *workspace.Workspace
binds *bind.Registry binds *bind.Registry
opt Options opt Options
man *manifest.Manifest
}
// methods lists the script methods the bindings give a component type, built once per kit.
func (k *Kit) methods(comp string) []manifest.CompMethod {
if k.man == nil {
m, err := manifest.FromRegistry(k.binds, k.Types.Schema())
if err != nil {
return nil
}
k.man = m
}
var out []manifest.CompMethod
for _, cm := range k.man.CompMethods {
if cm.Comp == comp {
out = append(out, cm)
}
}
return out
} }
func NewKit(p *project.Project, opt Options) (*Kit, error) { func NewKit(p *project.Project, opt Options) (*Kit, error) {
+3 -5
View File
@@ -41,7 +41,6 @@ type Config struct {
Editor bool Editor bool
FailOnScriptErrors bool FailOnScriptErrors bool
Catalog assembly.Catalog Catalog assembly.Catalog
Platform platform.Platform
Profile string Profile string
NoProfile bool NoProfile bool
} }
@@ -99,9 +98,6 @@ func (h *Host) build(cfg Config, mods []module.Module) error {
ecfg.LoopScope = cfg.LoopScope ecfg.LoopScope = cfg.LoopScope
} }
builder := engine.NewBuilder(ecfg).WithLogger(lg).Use(rmod) builder := engine.NewBuilder(ecfg).WithLogger(lg).Use(rmod)
if cfg.Platform != nil {
builder.WithPlatform(cfg.Platform)
}
if h.Types, err = assembly.NewTypes(); err != nil { if h.Types, err = assembly.NewTypes(); err != nil {
return err return err
} }
@@ -191,7 +187,6 @@ func (h *Host) build(cfg Config, mods []module.Module) error {
} }
h.Settings.SetNames(h.Sections.Names) h.Settings.SetNames(h.Sections.Names)
h.Engine.SetService(settings.ServiceID, h.Settings) h.Engine.SetService(settings.ServiceID, h.Settings)
h.Engine.OnClose(h.Modules.Shutdown)
if err = h.Modules.Init(&module.InitContext{Engine: h.Engine, Assets: h.Loader, Log: lg, Editor: cfg.Editor, Sections: h.Sections, Problems: h.Problems, Settings: h.Settings}); err != nil { if err = h.Modules.Init(&module.InitContext{Engine: h.Engine, Assets: h.Loader, Log: lg, Editor: cfg.Editor, Sections: h.Sections, Problems: h.Problems, Settings: h.Settings}); err != nil {
return err return err
} }
@@ -232,6 +227,9 @@ func (h *Host) Close() {
return return
} }
h.closed = true h.closed = true
if h.Modules != nil {
h.Modules.Shutdown()
}
if h.Engine != nil { if h.Engine != nil {
h.Engine.Close() h.Engine.Close()
} }
-70
View File
@@ -1,70 +0,0 @@
package docstool
import (
"fmt"
"io"
"strings"
"aego/app/playground"
"aego/engine/docs"
"aego/engine/module"
)
// CheckExamples loads the example scene of every module code, validates it and runs it headless; the code must be among the problems it reports (D146).
func CheckExamples(reg *docs.Registry, w io.Writer) int {
pg, err := playground.New()
if err != nil {
fmt.Fprintln(w, "examples: cannot start the docs runtime:", err)
return 1
}
pg.SetGame(playground.GameConfig{Headless: true, FrameTime: 1.0 / 60})
defer func() {
if g := pg.Game(); g != nil {
g.Close()
}
}()
bad := 0
for _, e := range reg.All() {
if e.Kind != docs.Code || e.Diag == nil || e.Diag.Example == "" {
continue
}
found, why := false, ""
code := ""
if e.Diag.ExampleScript != "" {
c, err := pg.TryCode(e.Diag.ExampleScript)
if err != nil {
fmt.Fprintf(w, "%s: error: example script does not compile: %v\n", e.ID, err)
bad++
continue
}
code = c
}
if res := pg.PlayScene("example-"+e.ID, []byte(e.Diag.Example), code, nil); !res.Started {
why = "does not load: " + res.Error
} else {
for _, d := range module.ValidateScene(pg.Game().Scene(), nil) {
found = found || d.Code == e.ID
}
if err := pg.Game().Step(tryFrames); err != nil {
why = "stops the engine: " + err.Error()
}
}
poll := pg.Poll()
for _, f := range poll.Faults {
found = found || f.Code == e.ID
}
for _, l := range poll.Log {
found = found || strings.Contains(l.Text, e.ID)
}
switch {
case why != "":
fmt.Fprintf(w, "%s: error: example %s %s\n", e.ID, e.Diag.ExampleFile, why)
bad++
case !found:
fmt.Fprintf(w, "%s: error: example of %s does not produce %s\n", e.ID, e.ID, e.ID)
bad++
}
}
pg.Stop()
return bad
}
-3
View File
@@ -76,9 +76,6 @@ func (e *Env) Check(strict bool) int {
if CheckTry(reg, e.Stdout) > 0 && code == 0 { if CheckTry(reg, e.Stdout) > 0 && code == 0 {
code = 1 code = 1
} }
if CheckExamples(reg, e.Stdout) > 0 && code == 0 {
code = 1
}
return code return code
} }
-13
View File
@@ -19,7 +19,6 @@ type request struct {
Component string `json:"component"` Component string `json:"component"`
Field string `json:"field"` Field string `json:"field"`
Value float64 `json:"value"` Value float64 `json:"value"`
Node string `json:"node"`
} }
type Reply struct { type Reply struct {
@@ -28,7 +27,6 @@ type Reply struct {
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
State *docsession.State `json:"state,omitempty"` State *docsession.State `json:"state,omitempty"`
Log []Line `json:"log,omitempty"` Log []Line `json:"log,omitempty"`
Pose []float64 `json:"pose,omitempty"`
} }
func (a *App) Call(method string, payload []byte) Reply { func (a *App) Call(method string, payload []byte) Reply {
@@ -66,17 +64,6 @@ func (a *App) Call(method string, payload []byte) Reply {
case "state": case "state":
st := s.State() st := s.State()
return Reply{State: &st} return Reply{State: &st}
case "pose":
sc := s.Controller().Scene()
if sc == nil {
return Reply{Error: "the scene is not running"}
}
h := sc.Find(sc.Root(), req.Node)
if h.IsZero() || !sc.IsAlive(h) {
return Reply{Error: "no node " + strconv.Quote(req.Node)}
}
p := sc.WorldPosition(h)
return Reply{Pose: []float64{float64(p.X), float64(p.Y)}}
case "log": case "log":
out := Reply{Log: a.lines} out := Reply{Log: a.lines}
a.lines = nil a.lines = nil
+1 -1
View File
@@ -179,7 +179,7 @@ func TestProfilerSectionsFollowPlay(t *testing.T) {
s.Step() s.Step()
} }
drawPanel(s, prof) drawPanel(s, prof)
expectSections(t, prof.Sections(), "Frame", "Memory", "Game", "Scripts", "Physics", "Audio", "Camera", "Gameplay") expectSections(t, prof.Sections(), "Frame", "Memory", "Game", "Scripts", "Physics", "Audio")
if _, err := ctx.Docs.Open("scenes/other.aescene"); err != nil { if _, err := ctx.Docs.Open("scenes/other.aescene"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
+1 -67
View File
@@ -7,10 +7,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"aego/core/ids"
"aego/editor/core/command" "aego/editor/core/command"
"aego/editor/core/context" "aego/editor/core/context"
"aego/editor/core/vectors"
"aego/editor/core/workspace" "aego/editor/core/workspace"
"aego/editor/panels/gameview" "aego/editor/panels/gameview"
"aego/editor/shell" "aego/editor/shell"
@@ -58,60 +56,7 @@ func (s *Session) shots(dir string) error {
} }
return selectNamed(ctx, "Player") return selectNamed(ctx, "Player")
}}, }},
{name: "hitbox-gizmo", frames: 60, setup: func(s *Session) error {
sc := ctx.Scene()
var span []ids.NodeID
for _, n := range []string{"EnemyLeft", "PatrolA1", "PatrolB1"} {
span = append(span, sc.ID(sc.Find(sc.Root(), n)))
}
ctx.Select(span...)
if err := ctx.Commands.Run(command.SceneFrame); err != nil {
return err
}
for i := 0; i < 30; i++ {
s.Step()
}
return selectNamed(ctx, "EnemyLeft")
}},
{name: "camera-follow", frames: 60, setup: func(s *Session) error {
sc := ctx.Scene()
ctx.Select(sc.ID(sc.Find(sc.Root(), "Camera")), sc.ID(sc.Find(sc.Root(), "Player")))
if err := ctx.Commands.Run(command.SceneFrame); err != nil {
return err
}
for i := 0; i < 30; i++ {
s.Step()
}
return selectNamed(ctx, "Camera")
}},
{name: "camera-zone", frames: 60, setup: func(s *Session) error {
sc := ctx.Scene()
ctx.Select(sc.ID(sc.Find(sc.Root(), "ArenaZone")), sc.ID(sc.Find(sc.Root(), "PatrolA2")), sc.ID(sc.Find(sc.Root(), "PatrolB2")), sc.ID(sc.Find(sc.Root(), "GroundRight")))
if err := ctx.Commands.Run(command.SceneFrame); err != nil {
return err
}
for i := 0; i < 30; i++ {
s.Step()
}
return selectNamed(ctx, "ArenaZone")
}},
{name: "clip-inspector", frames: 60, setup: func(s *Session) error {
id, ok := s.Pipeline.Lookup("assets/audio/music.ogg")
if !ok {
return fmt.Errorf("no assets/audio/music.ogg")
}
s.App.Data().Inspect(id)
return nil
}},
{name: "add-required", frames: 30, setup: func(s *Session) error {
if err := selectNamed(ctx, "PatrolA1"); err != nil {
return err
}
s.App.Inspector().OpenAdd("follow")
return nil
}},
{name: "tags", frames: 60, setup: func(s *Session) error { {name: "tags", frames: 60, setup: func(s *Session) error {
ctx.UI.CloseAllPopups()
return selectNamed(ctx, "Player") return selectNamed(ctx, "Player")
}}, }},
{name: "prefab", frames: 60, setup: func(s *Session) error { {name: "prefab", frames: 60, setup: func(s *Session) error {
@@ -138,23 +83,12 @@ func (s *Session) shots(dir string) error {
scene() scene()
return ctx.Commands.Run(command.PlayStart) return ctx.Commands.Run(command.PlayStart)
}}, }},
{name: "motion-play", frames: 30, setup: func(s *Session) error {
s.App.Workspace().Show(workspace.Inspector)
s.App.Inspector().ScrollTo("gameplay.Motion2D")
return selectNamed(ctx, "Player")
}},
{name: "mixer", frames: 120, setup: func(s *Session) error {
s.App.Workspace().Switch(workspace.Audio)
s.App.Workspace().Show(workspace.Mixer)
return nil
}},
{name: "physics-debug", frames: 30, setup: func(s *Session) error { {name: "physics-debug", frames: 30, setup: func(s *Session) error {
s.App.Workspace().Switch(workspace.Design)
ctx.Overlay.Set("physics.debug.contacts", true) ctx.Overlay.Set("physics.debug.contacts", true)
return ctx.Commands.Run(gameview.CommandOverlays) return ctx.Commands.Run(gameview.CommandOverlays)
}}, }},
{name: "demo", frames: 60, inset: 2, bottom: 28, setup: func(s *Session) error { {name: "demo", frames: 60, inset: 2, bottom: 28, setup: func(s *Session) error {
for _, f := range []string{gameview.CommandOverlays, vectors.CommandToggle} { for _, f := range []string{"physics.debug", "physics.debug.shapes", "physics.debug.contacts", "physics.debug.joints"} {
ctx.Overlay.Set(f, false) ctx.Overlay.Set(f, false)
} }
return ctx.Commands.Run(shell.CommandFullscreenPlay) return ctx.Commands.Run(shell.CommandFullscreenPlay)
+1
View File
@@ -26,6 +26,7 @@ canvas { display: block; width: 100%; height: 100%; outline: none; touch-action:
#mute .off, #mute.muted .on { display: none; } #mute .off, #mute.muted .on { display: none; }
#mute.muted .off { display: inline; } #mute.muted .off { display: inline; }
</style> </style>
<link rel="preload" href="game.wasm?v={{.Build}}" as="fetch" type="application/wasm" crossorigin>
</head> </head>
<body> <body>
<canvas id="game" data-aego tabindex="0" aria-label="{{.Title}}"></canvas> <canvas id="game" data-aego tabindex="0" aria-label="{{.Title}}"></canvas>
+5 -39
View File
@@ -71,22 +71,9 @@
status.textContent = `${mb(got)} of ${mb(total)}`; status.textContent = `${mb(got)} of ${mb(total)}`;
} }
// A request that delivers nothing for STALL ms is aborted and made again past the HTTP cache; a stuck cache entry must not leave the page on "Loading…". async function read(url) {
const STALL = 8000; const resp = await fetch(url);
const TRIES = 3;
async function readOnce(url, cache) {
const ctl = new AbortController();
let timer = setTimeout(() => ctl.abort(), STALL);
const kick = () => {
clearTimeout(timer);
timer = setTimeout(() => ctl.abort(), STALL);
};
let counted = 0;
try {
const resp = await fetch(url, { signal: ctl.signal, cache });
if (!resp.ok) throw new Error(`${url}: HTTP ${resp.status}`); if (!resp.ok) throw new Error(`${url}: HTTP ${resp.status}`);
kick();
if (!resp.body) { if (!resp.body) {
const all = new Uint8Array(await resp.arrayBuffer()); const all = new Uint8Array(await resp.arrayBuffer());
progress(all.byteLength); progress(all.byteLength);
@@ -98,10 +85,8 @@
for (;;) { for (;;) {
const { done, value } = await reader.read(); const { done, value } = await reader.read();
if (done) break; if (done) break;
kick();
parts.push(value); parts.push(value);
size += value.byteLength; size += value.byteLength;
counted += value.byteLength;
progress(value.byteLength); progress(value.byteLength);
} }
const out = new Uint8Array(size); const out = new Uint8Array(size);
@@ -111,26 +96,6 @@
at += p.byteLength; at += p.byteLength;
} }
return out; return out;
} catch (e) {
got -= counted;
throw e;
} finally {
clearTimeout(timer);
}
}
async function read(url) {
for (let i = 0; ; i++) {
try {
return await readOnce(url, i === 0 ? "default" : "reload");
} catch (e) {
if (!(e && e.name === "AbortError") || i + 1 >= TRIES) throw e;
}
}
}
async function readJSON(url) {
return JSON.parse(new TextDecoder().decode(await read(url)));
} }
async function each(items, fn) { async function each(items, fn) {
@@ -142,8 +107,9 @@
} }
async function start() { async function start() {
const list = await readJSON(`data/files.json?v=${build}`); const resp = await fetch(`data/files.json?v=${build}`);
got = 0; if (!resp.ok) throw new Error(`data/files.json: HTTP ${resp.status}`);
const list = await resp.json();
total = list.wasm + list.manifest.size + list.files.reduce((s, f) => s + f.size, 0); total = list.wasm + list.manifest.size + list.files.reduce((s, f) => s + f.size, 0);
const wasm = read(`game.wasm?v=${build}`); const wasm = read(`game.wasm?v=${build}`);
const manifest = read(`data/${list.manifest.path}?v=${build}`); const manifest = read(`data/${list.manifest.path}?v=${build}`);
-5
View File
@@ -198,11 +198,6 @@ func (g *Game) Engine() *engine.Engine {
return g.e return g.e
} }
// Scene is the scene Play loaded, nil before the first Play or after Stop.
func (g *Game) Scene() *scene.Scene {
return g.scene
}
func (g *Game) Play(name string, src []byte, mods []*bytecode.Module) error { func (g *Game) Play(name string, src []byte, mods []*bytecode.Module) error {
g.Stop() g.Stop()
rt := scriptruntime.New(scriptruntime.Options{Bindings: g.reg, Schema: g.types.Schema(), Log: g.log, VM: playmods.SandboxVM()}) rt := scriptruntime.New(scriptruntime.Options{Bindings: g.reg, Schema: g.types.Schema(), Log: g.log, VM: playmods.SandboxVM()})
-29
View File
@@ -9,10 +9,6 @@ import (
"aego/app/agent" "aego/app/agent"
"aego/core/log" "aego/core/log"
"aego/core/rewind" "aego/core/rewind"
"aego/engine/recorder"
"aego/modules/audio"
"aego/modules/camera"
"aego/modules/gameplay"
"aego/project" "aego/project"
"aego/sdk" "aego/sdk"
) )
@@ -49,7 +45,6 @@ func AgentRunner(mods ...sdk.Module) agent.Runner {
} }
sum, _, _ := WorldHash(a.Engine.MainWorld(), rewind.NewHasher()) sum, _, _ := WorldHash(a.Engine.MainWorld(), rewind.NewHasher())
rep.Hash = hex.EncodeToString(sum[:]) rep.Hash = hex.EncodeToString(sum[:])
rep.Counters = counters(a)
a.Close() a.Close()
rep.Log = logLines(buf.String()) rep.Log = logLines(buf.String())
return rep, nil return rep, nil
@@ -68,27 +63,3 @@ func logLines(s string) []string {
} }
return out return out
} }
// counters sums what the modules counted during the run, for scene run and run_headless.
func counters(a *App) map[string]uint64 {
out := map[string]uint64{}
w := a.Engine.MainWorld()
if s := gameplay.Of(w); s != nil {
st := s.Stats()
out["gameplay.steps"], out["gameplay.casts"], out["gameplay.hits"] = st.Steps, st.Casts, st.Hits
out["gameplay.deaths"], out["gameplay.respawns"], out["gameplay.pickups"] = st.Deaths, st.Respawns, st.Pickups
}
if c := camera.Of(w); c != nil {
c.Outputs(func(camera.OutputInfo) { out["camera.outputs"]++ })
}
if m := audio.ModuleOf(a.Engine); m != nil {
c, st := m.Counts(), m.Stats()
out["audio.finished"], out["audio.stolen"], out["audio.rejected"], out["audio.starved"] = c.Finished, c.Stolen, c.Rejected, c.Starved
out["audio.underruns"] = st.Underruns
}
if r := w.Recorder; r != nil {
counts := r.Counts()
out["events.audio"], out["events.gameplay"] = counts[recorder.Audio], counts[recorder.Gameplay]
}
return out
}
-1
View File
@@ -80,7 +80,6 @@ type bus struct {
target float32 target float32
delta float32 delta float32
ramp int ramp int
live bool
acc []float32 acc []float32
peak atomic.Uint32 peak atomic.Uint32
rms atomic.Uint32 rms atomic.Uint32
-84
View File
@@ -441,87 +441,3 @@ func TestVoicePositionAndKill(t *testing.T) {
t.Fatalf("kill: %+v, live %v", ev, m.Live(id)) t.Fatalf("kill: %+v, live %v", ev, m.Live(id))
} }
} }
func directPair(tb testing.TB, mem *Memory, loop bool) (*Mixer, voice, voice) {
m := newMixer(tb)
m.Play(PlayParams{Memory: mem, Gain: 0.7, Pan: 0.3, Loop: loop, FadeIn: 0.005})
buf := make([]float32, 64*2)
m.Mix(buf)
for i := range m.voices {
if m.voices[i].state == voicePlaying {
return m, m.voices[i], m.voices[i]
}
}
tb.Fatal("no playing voice")
return nil, voice{}, voice{}
}
// The direct path for pitch 1 gives the same samples, position, gain and end as Hermite, through fades, loops and the stop ramp.
func TestDirectPathMatchesHermite(t *testing.T) {
for _, ch := range []int{1, 2} {
for _, loop := range []bool{false, true} {
mem := sine(440, rate/20, ch, 0.5)
if loop {
mem.LoopStart, mem.LoopEnd = 100, mem.Frames()-50
}
m, a, b := directPair(t, mem, loop)
if b.step != 1 {
t.Fatalf("step %v, the direct path needs 1", b.step)
}
const n = 256
accA, accB := make([]float32, n*2), make([]float32, n*2)
for block := 0; block < 40; block++ {
if block == 20 {
a.rampTo(0, 300)
b.rampTo(0, 300)
a.stopping, b.stopping = true, true
}
clear(accA)
clear(accB)
da := m.renderMemory(&a, accA, n)
db := m.renderMemoryDirect(&b, accB, n)
if da != db || a.pos != b.pos || a.gain != b.gain {
t.Fatalf("ch=%d loop=%v block %d: done %v/%v pos %v/%v gain %v/%v", ch, loop, block, da, db, a.pos, b.pos, a.gain, b.gain)
}
for i := range accA {
if accA[i] != accB[i] {
t.Fatalf("ch=%d loop=%v block %d sample %d: %v != %v", ch, loop, block, i, accA[i], accB[i])
}
}
if da {
break
}
}
}
}
}
func loadedPitch1(tb testing.TB) (*Mixer, []float32) {
m := newMixer(tb)
mem := sine(440, rate/10, 1, 0.5)
for i := 0; i < 64; i++ {
m.Play(PlayParams{Memory: mem, Gain: 0.1, Pan: float32(i%3 - 1), Pitch: 1, Loop: true})
}
buf := make([]float32, 512*2)
m.Mix(buf)
if m.Active() != 64 {
tb.Fatalf("%d active voices", m.Active())
}
return m, buf
}
func TestMixPitch1Allocs(t *testing.T) {
m, buf := loadedPitch1(t)
if n := testing.AllocsPerRun(200, func() { m.Mix(buf) }); n != 0 {
t.Fatalf("Mix allocates %v times per call", n)
}
}
func BenchmarkMix64Pitch1(b *testing.B) {
m, buf := loadedPitch1(b)
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
m.Mix(buf)
}
}
+1 -91
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -141,7 +141,7 @@ func (p *Pump) Start() {
func (p *Pump) run(stop, done chan struct{}) { func (p *Pump) run(stop, done chan struct{}) {
defer close(done) defer close(done)
period := time.Duration(float64(p.cfg.Block*p.cfg.Target) / float64(p.cfg.SampleRate) / 6 * float64(time.Second)) period := time.Duration(float64(p.cfg.Block) / float64(p.cfg.SampleRate) / 2 * float64(time.Second))
t := time.NewTicker(max(period, time.Millisecond)) t := time.NewTicker(max(period, time.Millisecond))
defer t.Stop() defer t.Stop()
for { for {
-30
View File
@@ -102,33 +102,3 @@ func TestAgentSceneTreeJSON(t *testing.T) {
t.Fatalf("output %v:\n%s", err, out) t.Fatalf("output %v:\n%s", err, out)
} }
} }
// The top-down example needs no native module, so the CLI can run it from a copy.
func TestAgentSceneRunCounters(t *testing.T) {
dir := copyProject(t, "topdown")
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
stdout := os.Stdout
os.Stdout = w
code := dispatch([]string{"scene", "run", "scenes/main.aescene", "--frames", "300", "--json", "--project", dir})
os.Stdout = stdout
w.Close()
out, _ := io.ReadAll(r)
if code != 0 {
t.Fatalf("exit %d:\n%s", code, out)
}
var v struct {
Value struct {
Counters map[string]uint64 `json:"counters"`
} `json:"value"`
}
if err := json.Unmarshal(out, &v); err != nil {
t.Fatalf("output %v:\n%s", err, out)
}
c := v.Value.Counters
if c["gameplay.steps"] == 0 || c["camera.outputs"] != 1 {
t.Fatalf("counters %v, want gameplay steps and one camera output", c)
}
}
-37
View File
@@ -1,37 +0,0 @@
# Contributing
## Build and run
```sh
bash tools/bootstrap-native.sh
make cli
.cache/bin/aego edit examples/platformer
```
`make res` regenerates icons, previews and ES shaders; `make golden-update` rewrites golden images after an intended UI change.
## Check levels
| Level | When | Command |
|---|---|---|
| 1 | after an edit | `make quick`: build and short tests of the packages your change touches and everything that imports them |
| 2 | before a commit | `make affected`, plus `make check-camera`, `check-audio`, `check-gameplay`, `check-mutate`, `check-editor`, `check-web` or `check-docs` for the areas you touched, and `make golden` after UI changes |
| 3 | before a push or a tag | `make ci` |
`tools/affected.sh [base]` lists the affected packages (default base `origin/master`, uncommitted files included). The CI server always runs the full `make ci`.
## Rules for engine code
- **Modules talk through contracts.** A module never imports another module: shared types and services live in `contracts/` (`physics2d`, `camera2d`, `scriptcall`, `signals`, `debugdraw`, …). `tools/depcheck` enforces the import rules; an exception goes into `tools/depcheck/exceptions.txt` with its file and reason.
- **Kernel stays neutral.** Kernel, editor and `sdk` never name a built-in module, its components or its diagnostic codes in string literals; names come from the module catalog.
- **Every component has a Try it scene.** It lives at `res/docs/try/<Component>.aescene` (with `<Component>.aes` and prefabs next to it when needed), shows the component's own effect and runs without warnings; `aego docs check` fails without it.
- **Internal state is not a component.** Controller state lives in unexported fields of the component it belongs to or in a world service. A component that exists only to carry state for a service is registered `Hidden`: it is not in Create, Add Component, Try it or the docs.
- **Requirements add, they do not block.** Declare `Requires`; Add Component, `scene add-comp`, Paste and presets add the missing components in the same undo step.
- **Every diagnostic code has a page with an example.** Component codes (`CAM`, `AUD`, `GMP`, `PHY`, …) also have an example scene that produces the code; `aego docs check` fails without one. Asset codes say `scope: asset`, codes that only an edit during Play produces say `scope: play`.
- **Nodes are labelled, not pathed.** UI shows nodes with `props.NodeLabel` (icon and name, the path in the tooltip).
- **Editor edits go through history.** Every edit is a history command, normally `commands.Mutation`, so Undo, the Play changes diff and agents see it.
- **Hot paths do not allocate.** Systems, mixers and controllers allocate nothing per frame; add `testing.AllocsPerRun` checks and a benchmark next to new hot code.
## Commits
One commit per sub-stage, named `Stage <n><x>: <what>`; decisions go into `ARCHITECTURE.md` as `D<number>` in the same commit.
+1 -13
View File
@@ -97,16 +97,6 @@ sdk.Component[Wind](r, sdk.Name("weather.Wind")).
`engine/problems` collects `sdk.Diagnostic{Owner, Code, Severity, Message, Subject, BlocksPlay}` from sources with a revision; the service rebuilds the list only when a revision changes. `sdk.Diagnostics(ctx)` in `Init` returns the service; a module adds runtime diagnostics as a `sdk.ProblemSource` from `AttachWorld` and removes it in `DetachWorld`. The Problems panel shows them with the owner as category; an error with `BlocksPlay` stops Play (D210). `engine/problems` collects `sdk.Diagnostic{Owner, Code, Severity, Message, Subject, BlocksPlay}` from sources with a revision; the service rebuilds the list only when a revision changes. `sdk.Diagnostics(ctx)` in `Init` returns the service; a module adds runtime diagnostics as a `sdk.ProblemSource` from `AttachWorld` and removes it in `DetachWorld`. The Problems panel shows them with the owner as category; an error with `BlocksPlay` stops Play (D210).
Every code a module reports is listed in `Info.Codes` and has a page `res/docs/diagnostics/<CODE>.md` with front matter `code`, `title`, `severity` (`error`, `warning` or `info`) and either `example: <CODE>.aescene`, a scene next to the page that produces the code (with `<CODE>.aes` next to it when a script has to act, as for PHY007), `scope: asset` for codes that come from importing or checking an asset, or `scope: play` for codes that only an edit during Play produces (PHY006). `aego docs check` fails on a declared code without a page, a page whose code is not declared, a component code without an example, and an example that does not produce its code after `module.ValidateScene` and 60 headless frames. The pages appear under Reference → Diagnostics next to the script codes (D146, D316).
### Contracts between modules
A module never imports another module. What two modules share lives in `contracts/<name>` as plain types, a service ID and an interface; the provider registers the service in its world, the consumer looks it up with `engine.ServiceOf` and works without it.
- **Provisions.** A value handed over at load time, keyed by a kind. `camera2d.AccessibilityKind` (`"camera2d.accessibility"`) carries the IDs of the Screen Shake and Reduce Motion settings: the settings module provides them with `r.Provide(camera2d.AccessibilityKind, camera2d.Accessibility{...})`, the camera module reads `ctx.Provided(camera2d.AccessibilityKind)` in `Init` and scales shake by them every frame.
- **`physics2d.Queries`** (`physics2d.ServiceID`). Ray, point, circle and box queries; shape casts `CastCircle`, `CastBox`, `CastCapsule` and `CastBody`, which sweeps every non-trigger collider of a body and returns the first hit with its fraction, normal and, when the shapes already overlap, `Initial` and `Depth`; `Penetration` lists the overlaps of a body for depenetration; `BodyPose`, `BodyVelocity`, `MoveKinematic` (moves a kinematic body so contacts see its velocity), `Teleport`, `ApplyImpulse` and `Gravity`. `QueryFilter` selects layers and triggers; `SkipDynamic` ignores dynamic bodies, and `ExcludeNode` and `ExcludeBody` skip the caller's own collider or body. The gameplay controller is written only against this interface.
- **`scriptcall.Caller`** (`scriptcall.ServiceID`). `Method(node, name, params)` finds a plain method by name in the scripts on a node and returns a `Ref`; `Call(&ref, args)` runs it. A hot reload starts a new `Generation`, which makes every older `Ref` stale: look the method up again when `Ref.Gen` differs. The State Machine finds its hooks this way.
### Resources ### Resources
Module icons are named `<owner>.*` or `cat.<owner>`; a clash with the engine atlas or another module is a build error. Built-in modules list their icon sources through `IconDir()`. Docs registered with `Registry.Docs` carry the module as owner and are left out of runtime builds (D211). Module icons are named `<owner>.*` or `cat.<owner>`; a clash with the engine atlas or another module is a build error. Built-in modules list their icon sources through `IconDir()`. Docs registered with `Registry.Docs` carry the module as owner and are left out of runtime builds (D211).
@@ -179,6 +169,4 @@ A panel gets a fresh `Frame` every draw (`UI`, drag and drop, time, focus). Anyt
A module can give any of its components a live example on the documentation site: put a scene in `res/docs/try/<Component>.aescene`, named after the last part of the component ID (`try/Body2D.aescene` for `physics.Body2D`). The page of that component gets a Try it block that runs the scene in the browser with the Inspector, selecting the first node that has the component; a guide page can show a scene with `try: try/name.aescene` and `try_select: module.Component` in its front matter. A module can give any of its components a live example on the documentation site: put a scene in `res/docs/try/<Component>.aescene`, named after the last part of the component ID (`try/Body2D.aescene` for `physics.Body2D`). The page of that component gets a Try it block that runs the scene in the browser with the Inspector, selecting the first node that has the component; a guide page can show a scene with `try: try/name.aescene` and `try_select: module.Component` in its front matter.
Try scenes run on the docs runtime catalog (core, input, render2d, render3d, scripting, physics, debug, tween, cameras, gameplay, spawn, savegame and audio) with builtin assets only. A script `<Component>.aes` and prefabs `<Component>.<name>.aeprefab` (with their `.meta`) next to the scene are compiled and mounted with it. `aego docs check` loads each one, runs it for 60 frames and fails on a load error, a warning or a fault, and on a scene of a module outside the catalog. Keep the scene to what the component needs, put the documented node first, and add a camera. Try scenes run on the sandbox catalog (core, input, render2d, scripting, physics, debug) with builtin assets only and no scripts. `aego docs check` loads each one, runs it for 60 frames and fails on a load error, a warning or a fault, and on a scene of a module outside the sandbox. Keep the scene to what the component needs, put the documented node first, and add a camera.
Every visible component needs one. A component registered `Hidden`, which only carries state for a service, has none and is left out of Create, Add Component, Try it and the docs. The rules for engine code, including these, are in [contributing.md](contributing.md).
-4
View File
@@ -61,10 +61,8 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
| `edit.paste` | engine | 16 | Paste (nodes, assets, inspector field) | ok | | `edit.paste` | engine | 16 | Paste (nodes, assets, inspector field) | ok |
| `edit.rename` | engine | 16 | Rename (nodes, assets, workspace) | ok | | `edit.rename` | engine | 16 | Rename (nodes, assets, workspace) | ok |
| `edit.select_all` | engine | 16 | Edit → Select All | ok | | `edit.select_all` | engine | 16 | Edit → Select All | ok |
| `file.audio` | engine | 16 | Documentation: sound clips page | ok |
| `file.autosave` | engine | 16 | File → Autosave Scripts | ok | | `file.autosave` | engine | 16 | File → Autosave Scripts | ok |
| `file.data` | engine | 24 | Assets: data asset tiles | ok | | `file.data` | engine | 24 | Assets: data asset tiles | ok |
| `file.download` | engine | 16 | Documentation: Install and run page | ok |
| `file.folder` | engine | 24 | Assets: folders | ok | | `file.folder` | engine | 24 | Assets: folders | ok |
| `file.mesh` | engine | 24 | Assets: mesh tiles | ok | | `file.mesh` | engine | 24 | Assets: mesh tiles | ok |
| `file.open` | engine | 16 | Assets menu: Open | ok | | `file.open` | engine | 16 | Assets menu: Open | ok |
@@ -136,7 +134,6 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
| `prefab.variant` | engine | 16 | Assets menu: Create Variant | ok | | `prefab.variant` | engine | 16 | Assets menu: Create Variant | ok |
| `shape.cube` | engine | 16 | template render3d.cube | ok | | `shape.cube` | engine | 16 | template render3d.cube | ok |
| `shape.cylinder` | engine | 16 | template render3d.cylinder | ok | | `shape.cylinder` | engine | 16 | template render3d.cylinder | ok |
| `shape.dice` | engine | 16 | Documentation: random numbers module | ok |
| `shape.plane` | engine | 16 | template render3d.plane | ok | | `shape.plane` | engine | 16 | template render3d.plane | ok |
| `shape.sphere` | engine | 16 | template render3d.sphere | ok | | `shape.sphere` | engine | 16 | template render3d.sphere | ok |
| `status.busy` | engine | 14 | Task Center: running task | ok | | `status.busy` | engine | 14 | Task Center: running task | ok |
@@ -154,7 +151,6 @@ Sources are `<id>.svg` files in `engine/res/icons/src` and `modules/<id>/res/ico
| `ui.chevron_down` | engine | 16 | trees, combos and drop-downs | ok | | `ui.chevron_down` | engine | 16 | trees, combos and drop-downs | ok |
| `ui.chevron_right` | engine | 16 | trees and submenus | ok | | `ui.chevron_right` | engine | 16 | trees and submenus | ok |
| `ui.close` | engine | 16 | tabs, chips and dialogs | ok | | `ui.close` | engine | 16 | tabs, chips and dialogs | ok |
| `ui.curve` | engine | 16 | Documentation: curves module | ok |
| `ui.lock` | engine | 16 | Inspector: locked | ok | | `ui.lock` | engine | 16 | Inspector: locked | ok |
| `ui.minus` | engine | 16 | list editors: remove | ok | | `ui.minus` | engine | 16 | list editors: remove | ok |
| `ui.more` | engine | 16 | card menus | ok | | `ui.more` | engine | 16 | card menus | ok |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 126 KiB

-3
View File
@@ -30,9 +30,6 @@ type ProfilerSection struct {
Draw func() Draw func()
} }
// OverlayGame is the flag of Window → Game View Overlays; module overlays and debug channels in the Game View follow it.
const OverlayGame = "view.game.overlays"
type Overlays struct { type Overlays struct {
on map[string]bool on map[string]bool
Scale float32 Scale float32
-4
View File
@@ -58,9 +58,7 @@ var Editor = []module.IconUse{
use("edit.paste", 16, "Paste (nodes, assets, inspector field)"), use("edit.paste", 16, "Paste (nodes, assets, inspector field)"),
use("edit.rename", 16, "Rename (nodes, assets, workspace)"), use("edit.rename", 16, "Rename (nodes, assets, workspace)"),
use("edit.select_all", 16, "Edit → Select All"), use("edit.select_all", 16, "Edit → Select All"),
use("file.audio", 16, "Documentation: sound clips page"),
use("file.autosave", 16, "File → Autosave Scripts"), use("file.autosave", 16, "File → Autosave Scripts"),
use("file.download", 16, "Documentation: Install and run page"),
use("file.data", 24, "Assets: data asset tiles"), use("file.data", 24, "Assets: data asset tiles"),
use("file.folder", 24, "Assets: folders"), use("file.folder", 24, "Assets: folders"),
use("file.mesh", 24, "Assets: mesh tiles"), use("file.mesh", 24, "Assets: mesh tiles"),
@@ -76,8 +74,6 @@ var Editor = []module.IconUse{
use("file.texture", 24, "Assets: texture tiles"), use("file.texture", 24, "Assets: texture tiles"),
use("file.unknown", 24, "Assets: tiles of other kinds"), use("file.unknown", 24, "Assets: tiles of other kinds"),
use("help.about", 16, "Help → About Aego"), use("help.about", 16, "Help → About Aego"),
use("shape.dice", 16, "Documentation: random numbers module"),
use("ui.curve", 16, "Documentation: curves module"),
use("help.book", 16, "Documentation tab"), use("help.book", 16, "Documentation tab"),
use("log.error", 16, "Console: errors"), use("log.error", 16, "Console: errors"),
use("log.info", 16, "Console: info"), use("log.info", 16, "Console: info"),
-21
View File
@@ -50,27 +50,6 @@ type Registry struct {
owned map[string]*owned owned map[string]*owned
disabled map[string]string disabled map[string]string
pending []pending pending []pending
ticks []tick
}
type tick struct {
ext string
fn func()
}
// Tick runs fn once per editor frame, before the panels draw, whether or not any view is visible.
func (r *Registry) Tick(fn func()) {
r.ticks = append(r.ticks, tick{ext: r.ext(), fn: fn})
}
// RunTicks calls the Tick hooks of the extensions that are not disabled.
func (r *Registry) RunTicks() {
for _, t := range r.ticks {
if _, off := r.disabled[t.ext]; off {
continue
}
Safe(t.ext, r, t.fn)
}
} }
func New(d Deps) *Registry { func New(d Deps) *Registry {
+3 -5
View File
@@ -20,7 +20,7 @@ import (
const ID = "events" const ID = "events"
var shown = [...]recorder.Kind{recorder.Signal, recorder.Event, recorder.Fault, recorder.Spawn, recorder.Despawn, recorder.SceneChange, recorder.Input, recorder.Audio, recorder.Gameplay} var shown = [...]recorder.Kind{recorder.Signal, recorder.Event, recorder.Fault, recorder.Spawn, recorder.Despawn, recorder.SceneChange, recorder.Input, recorder.Audio}
type Deps struct { type Deps struct {
World func() *engine.World World func() *engine.World
@@ -174,11 +174,9 @@ func (p *Panel) toolbar(ui *core.Context, s *scene.Scene) {
widgets.ToggleButton(ui, core.IDSeq(ui.ID("kind"), i), mathx.Rect{X: x, Y: row.Y, W: w, H: row.H}, k.String(), &p.show[k]) widgets.ToggleButton(ui, core.IDSeq(ui.ID("kind"), i), mathx.Rect{X: x, Y: row.Y, W: w, H: row.H}, k.String(), &p.show[k])
x += w + 4 x += w + 4
} }
if row.X+row.W-x < 120 { if row.X+row.W-x > 80 {
row = ui.Cursor().Next(0)
x = row.X
}
widgets.SearchField(ui, ui.ID("search"), mathx.Rect{X: x, Y: row.Y, W: row.X + row.W - x, H: row.H}, &p.search) widgets.SearchField(ui, ui.ID("search"), mathx.Rect{X: x, Y: row.Y, W: row.X + row.W - x, H: row.H}, &p.search)
}
row = ui.Cursor().Next(0) row = ui.Cursor().Next(0)
x = row.X x = row.X
place := func(w float32) mathx.Rect { place := func(w float32) mathx.Rect {
+1 -2
View File
@@ -18,7 +18,7 @@ import (
"aego/ui/widgets" "aego/ui/widgets"
) )
const CommandOverlays = context.OverlayGame const CommandOverlays = "view.game.overlays"
type Panel struct { type Panel struct {
play *play.Controller play *play.Controller
@@ -77,7 +77,6 @@ func (p *Panel) Close() {
func (p *Panel) Draw(f *services.Frame, r mathx.Rect) { func (p *Panel) Draw(f *services.Frame, r mathx.Rect) {
ui := f.UI ui := f.UI
p.tl.Apply()
th := &ui.Theme th := &ui.Theme
if p.pool == nil { if p.pool == nil {
p.hook = p.render p.hook = p.render
+17 -42
View File
@@ -24,51 +24,16 @@ type TimelineBar struct {
bar mathx.Rect bar mathx.Rect
dragging bool dragging bool
dragTo int dragTo int
pending int scrubbed int
sought int
release bool
seekErr string seekErr string
} }
func NewTimelineBar(src Scrubber) *TimelineBar { func NewTimelineBar(src Scrubber) *TimelineBar {
return &TimelineBar{src: src, pending: -1, sought: -1} return &TimelineBar{src: src}
} }
func (b *TimelineBar) Reset() { func (b *TimelineBar) Reset() {
b.bar, b.dragging, b.pending, b.sought, b.release = mathx.Rect{}, false, -1, -1, false b.bar, b.dragging = mathx.Rect{}, false
}
// hold records where the drag is; the seek waits for Apply, so any number of mouse moves in a frame cost one seek.
func (b *TimelineBar) hold(n int, down bool) {
b.dragTo, b.pending = n, n
if !down {
b.dragging, b.release = false, true
}
}
// Apply runs at most one seek per frame: a scrub to the latest drag position, or the final seek after release.
func (b *TimelineBar) Apply() {
if b.pending < 0 {
return
}
n := b.pending
if b.release {
b.release, b.pending = false, -1
if n != b.sought && b.seekErr == "" {
if err := b.src.Seek(n); err != nil {
b.seekErr = err.Error()
}
}
b.sought = -1
return
}
if n == b.sought || b.seekErr != "" {
return
}
b.sought = n
if err := b.src.Scrub(n); err != nil {
b.seekErr = err.Error()
}
} }
func (b *TimelineBar) Clear() { func (b *TimelineBar) Clear() {
@@ -158,17 +123,27 @@ func (b *TimelineBar) Draw(ui *core.Context, r mathx.Rect) {
id := ui.ID("timeline") id := ui.ID("timeline")
hit := mathx.Rect{X: track.X - 4, Y: bar.Y, W: track.W + 8, H: bar.H} hit := mathx.Rect{X: track.X - 4, Y: bar.Y, W: track.W + 8, H: bar.H}
if ui.Pressed(id, hit) { if ui.Pressed(id, hit) {
b.dragging, b.seekErr, b.sought, b.release = true, "", -1, false b.dragging, b.seekErr, b.scrubbed = true, "", -1
} }
cur := tl.Cursor cur := tl.Cursor
if tl.Beyond { if tl.Beyond {
cur = tl.Frames cur = tl.Frames
} }
if b.dragging { if b.dragging {
b.hold(min(max(frameAt(ui.Mouse().X), tl.Floor), tl.Usable), ui.MouseDown(input.MouseLeft)) b.dragTo = min(max(frameAt(ui.Mouse().X), tl.Floor), tl.Usable)
}
if b.dragging || b.pending >= 0 {
cur = b.dragTo cur = b.dragTo
if b.dragTo != b.scrubbed && b.seekErr == "" {
b.scrubbed = b.dragTo
if err := b.src.Scrub(b.dragTo); err != nil {
b.seekErr = err.Error()
}
}
if !ui.MouseDown(input.MouseLeft) {
b.dragging = false
if err := b.src.Seek(b.dragTo); err != nil {
b.seekErr = err.Error()
}
}
} }
hx := xAt(cur) hx := xAt(cur)
ui.Rect(mathx.Rect{X: hx - 2, Y: bar.Y + 3, W: 4, H: bar.H - 6}, th.Accent) ui.Rect(mathx.Rect{X: hx - 2, Y: bar.Y + 3, W: 4, H: bar.H - 6}, th.Accent)
-48
View File
@@ -1,48 +0,0 @@
package gameview
import (
"testing"
"aego/editor/core/play"
)
type countScrub struct {
seeks, scrubs []int
}
func (c *countScrub) Timeline() play.Timeline { return play.Timeline{Frames: 100, Usable: 100} }
func (c *countScrub) State() play.State { return play.Paused }
func (c *countScrub) Ticks() uint64 { return 0 }
func (c *countScrub) Seek(n int) error { c.seeks = append(c.seeks, n); return nil }
func (c *countScrub) Scrub(n int) error { c.scrubs = append(c.scrubs, n); return nil }
func TestDragSeeksOncePerFrame(t *testing.T) {
src := &countScrub{}
b := NewTimelineBar(src)
b.dragging = true
for i := range 20 {
b.hold(10+i, true)
}
b.Apply()
if len(src.scrubs) != 1 || src.scrubs[0] != 29 || len(src.seeks) != 0 {
t.Fatalf("20 moves in a frame: scrubs %v seeks %v, want one scrub to 29", src.scrubs, src.seeks)
}
b.Apply()
if len(src.scrubs) != 1 {
t.Fatalf("a frame without moves scrubbed again: %v", src.scrubs)
}
b.hold(40, true)
b.hold(41, false)
b.Apply()
if len(src.seeks) != 1 || src.seeks[0] != 41 || len(src.scrubs) != 1 {
t.Fatalf("release: scrubs %v seeks %v, want one final seek to 41", src.scrubs, src.seeks)
}
b.dragging = true
b.hold(50, true)
b.Apply()
b.hold(50, false)
b.Apply()
if len(src.seeks) != 1 {
t.Fatalf("a release where the last scrub already is seeks again: %v", src.seeks)
}
}
-23
View File
@@ -58,9 +58,6 @@ type Deps struct {
} }
type Panel struct { type Panel struct {
scrollTo string
top float32
openAdd *string
reg *inspect.Registry reg *inspect.Registry
d Deps d Deps
f *services.Frame f *services.Frame
@@ -216,7 +213,6 @@ func (p *Panel) Draw(f *services.Frame, r mathx.Rect) {
p.scroll = mathx.Clamp(p.scroll, 0, max(0, p.content-r.H+th.Padding*2)) p.scroll = mathx.Clamp(p.scroll, 0, max(0, p.content-r.H+th.Padding*2))
cur := ui.Cursor() cur := ui.Cursor()
top := cur.Y top := cur.Y
p.top = top
cur.Y -= p.scroll cur.Y -= p.scroll
ui.PushClip(r) ui.PushClip(r)
p.header(d) p.header(d)
@@ -607,10 +603,6 @@ func (p *Panel) component(d *document.SceneDocument, t schema.TypeID) {
if !ok { if !ok {
return return
} }
if p.scrollTo == td.FullName {
p.scroll += ui.Cursor().Y - p.top
p.scrollTo = ""
}
ui.PushRawID(core.ID(t)) ui.PushRawID(core.ID(t))
defer ui.PopID() defer ui.PopID()
meta := p.reg.Meta(td) meta := p.reg.Meta(td)
@@ -715,16 +707,6 @@ func inScene(s *scene.Scene, t schema.TypeID) bool {
return false return false
} }
// ScrollTo brings the card of a component type to the top on the next draw, for the documentation screenshots.
func (p *Panel) ScrollTo(fullName string) {
p.scrollTo = fullName
}
// OpenAdd opens Add Component with the search text on the next draw, for the documentation screenshots.
func (p *Panel) OpenAdd(query string) {
p.openAdd = &query
}
func (p *Panel) addComponent(d *document.SceneDocument) { func (p *Panel) addComponent(d *document.SceneDocument) {
ui := p.f.UI ui := p.f.UI
th := &ui.Theme th := &ui.Theme
@@ -742,11 +724,6 @@ func (p *Panel) addComponent(d *document.SceneDocument) {
p.focusSearch = true p.focusSearch = true
} }
} }
if p.openAdd != nil {
ui.CloseAllPopups()
ui.OpenPopup(id, mathx.Vec2{X: row.X, Y: row.Y + row.H})
p.search, p.openAdd = *p.openAdd, nil
}
ui.Rect(row, ui.WidgetColor(id)) ui.Rect(row, ui.WidgetColor(id))
ui.Outline(row, th.AccentDim) ui.Outline(row, th.AccentDim)
ui.TextIn(row, "Add Component", th.Text, core.AlignCenter) ui.TextIn(row, "Add Component", th.Text, core.AlignCenter)

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