Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
d715bc0625
|
@@ -26,7 +26,7 @@ scripting engine for game authors.
|
|||||||
| [the issue tracker](https://source.starfort.tech/andrew/akbasic/issues) | **Outstanding defects and gaps.** Labelled by kind and blast radius; `status::grooming` means the scope is not settled yet |
|
| [the issue tracker](https://source.starfort.tech/andrew/akbasic/issues) | **Outstanding defects and gaps.** Labelled by kind and blast radius; `status::grooming` means the scope is not settled yet |
|
||||||
| [`TODO.md`](TODO.md) | The record: settled design decisions, the deviation register, defects already fixed, and the reasoning behind the measurements. §0.1 first — it retires the byte-for-byte fidelity constraint several later sections were written on |
|
| [`TODO.md`](TODO.md) | The record: settled design decisions, the deviation register, defects already fixed, and the reasoning behind the measurements. §0.1 first — it retires the byte-for-byte fidelity constraint several later sections were written on |
|
||||||
| [`README.md`](README.md) | What the project is and why, for somebody who has not seen it |
|
| [`README.md`](README.md) | What the project is and why, for somebody who has not seen it |
|
||||||
| [`docs/`](docs/README.md) | The language itself: twenty-one chapters, verb and function reference. [Chapter 14](docs/14-architecture.md) is the interpreter's architecture — the step loop, the pools, the two kinds of error, and how to debug it. [Chapter 15](docs/15-error-codes.md) is the error-code appendix. [Chapters 17](docs/17-tutorial-breakout.md) and [18](docs/18-tutorial-breakout-artwork.md) are tutorials that build the games in `examples/breakout/`; [Chapters 20](docs/20-tutorial-galaga.md) and [21](docs/21-tutorial-galaga-enemies.md) build the embedding host in `examples/galaga/` |
|
| [`docs/`](docs/README.md) | The language itself: eighteen chapters, verb and function reference. [Chapter 14](docs/14-architecture.md) is the interpreter's architecture — the step loop, the pools, the two kinds of error, and how to debug it. [Chapter 15](docs/15-error-codes.md) is the error-code appendix. [Chapters 17](docs/17-tutorial-breakout.md) and [18](docs/18-tutorial-breakout-artwork.md) are tutorials that build the games in `examples/breakout/` |
|
||||||
| `deps/libakerror/AGENTS.md` | The `ATTEMPT`/`CLEANUP`/`PROCESS`/`HANDLE`/`FINISH` protocol, authoritatively |
|
| `deps/libakerror/AGENTS.md` | The `ATTEMPT`/`CLEANUP`/`PROCESS`/`HANDLE`/`FINISH` protocol, authoritatively |
|
||||||
| `deps/libakerror/UPGRADING.md` | 1.0.0's status registry. Required before writing an error code |
|
| `deps/libakerror/UPGRADING.md` | 1.0.0's status registry. Required before writing an error code |
|
||||||
| `deps/<library>/AGENTS.md` | Per-repo rules. Read the relevant one **before editing a submodule** |
|
| `deps/<library>/AGENTS.md` | Per-repo rules. Read the relevant one **before editing a submodule** |
|
||||||
|
|||||||
@@ -268,69 +268,6 @@ if(AKBASIC_BUILD_EXAMPLES)
|
|||||||
endforeach()
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# The galaga example: a C game on libakgl with the interpreter embedded as its
|
|
||||||
# enemy-behavior engine. Chapters 20 and 21 build it from an empty file, so it
|
|
||||||
# is compiled and run by every AKGL build rather than rotting in a document.
|
|
||||||
# The asset, script and font paths are baked in so the smoke test can launch
|
|
||||||
# from any working directory; --assets and --script override them at runtime.
|
|
||||||
if(AKBASIC_BUILD_EXAMPLES AND AKBASIC_WITH_AKGL)
|
|
||||||
add_executable(akbasic_example_galaga
|
|
||||||
examples/galaga/main.c
|
|
||||||
examples/galaga/script.c
|
|
||||||
examples/galaga/enemies.c
|
|
||||||
examples/galaga/player.c)
|
|
||||||
target_compile_options(akbasic_example_galaga PRIVATE -Wall -Wextra)
|
|
||||||
target_compile_definitions(akbasic_example_galaga PRIVATE
|
|
||||||
GALAGA_ASSET_DIR="${CMAKE_CURRENT_SOURCE_DIR}/examples/galaga/assets"
|
|
||||||
GALAGA_SCRIPT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/examples/galaga/galaga.bas"
|
|
||||||
GALAGA_FONT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/assets/fonts/C64_Pro_Mono-STYLE.ttf")
|
|
||||||
target_link_libraries(akbasic_example_galaga PRIVATE akbasic akgl
|
|
||||||
SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image)
|
|
||||||
akbasic_instrument(akbasic_example_galaga)
|
|
||||||
# Ten seconds of scripted play under the headless drivers: the script boots,
|
|
||||||
# a wave enters and forms, the autoplay pilot shoots at it, and the program
|
|
||||||
# tears down and exits 0. A tutorial that stops working fails here rather
|
|
||||||
# than in front of a reader.
|
|
||||||
_add_test(NAME example_galaga COMMAND akbasic_example_galaga --frames 600 --autoplay)
|
|
||||||
_set_tests_properties(example_galaga PROPERTIES TIMEOUT 120
|
|
||||||
ENVIRONMENT "SDL_VIDEODRIVER=dummy;SDL_AUDIODRIVER=dummy;SDL_RENDER_DRIVER=software")
|
|
||||||
|
|
||||||
# The boundary's round-trip test: links the real script.c and the real
|
|
||||||
# galaga.bas, and fails the moment the two sides of the interop disagree.
|
|
||||||
add_executable(akbasic_example_galaga_interop
|
|
||||||
examples/galaga/interop_test.c
|
|
||||||
examples/galaga/script.c)
|
|
||||||
target_compile_options(akbasic_example_galaga_interop PRIVATE -Wall -Wextra)
|
|
||||||
target_compile_definitions(akbasic_example_galaga_interop PRIVATE
|
|
||||||
GALAGA_SCRIPT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/examples/galaga/galaga.bas")
|
|
||||||
target_link_libraries(akbasic_example_galaga_interop PRIVATE akbasic akgl
|
|
||||||
SDL3::SDL3 m)
|
|
||||||
akbasic_instrument(akbasic_example_galaga_interop)
|
|
||||||
_add_test(NAME example_galaga_interop COMMAND akbasic_example_galaga_interop)
|
|
||||||
_set_tests_properties(example_galaga_interop PROPERTIES TIMEOUT 120)
|
|
||||||
|
|
||||||
# Regenerating the game figures in docs/ is a deliberate act, never part of
|
|
||||||
# a build, for the same reason docs_screenshots is: the PNGs are checked in.
|
|
||||||
# Wall-clock dt makes each regeneration differ by a few pixels of starfield,
|
|
||||||
# so expect a binary diff every time this runs; commit one only when the
|
|
||||||
# content changed on purpose. (docs_galaga_figures, not docs_game_figures:
|
|
||||||
# the libakgl submodule already owns that target name.)
|
|
||||||
add_custom_target(docs_galaga_figures
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E env SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy
|
|
||||||
SDL_RENDER_DRIVER=software
|
|
||||||
$<TARGET_FILE:akbasic_example_galaga> --frames 40
|
|
||||||
--screenshot "${CMAKE_CURRENT_SOURCE_DIR}/docs/images/galaga-title.png"
|
|
||||||
--screenshot-frame 30
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E env SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy
|
|
||||||
SDL_RENDER_DRIVER=software
|
|
||||||
$<TARGET_FILE:akbasic_example_galaga> --autoplay --frames 370
|
|
||||||
--screenshot "${CMAKE_CURRENT_SOURCE_DIR}/docs/images/galaga-wave.png"
|
|
||||||
--screenshot-frame 360
|
|
||||||
DEPENDS akbasic_example_galaga
|
|
||||||
COMMENT "Regenerating the galaga figures in docs/images"
|
|
||||||
VERBATIM)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Tests.
|
# Tests.
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ version are catalogued in [`TODO.md`](TODO.md) and summarised for a BASIC progra
|
|||||||
|
|
||||||
| | |
|
| | |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [`docs/`](docs/README.md) | The guide: twenty-one chapters, the language then each hardware area then a reference section for every verb and function, [Chapter 14](docs/14-architecture.md) on the interpreter's own architecture, [Chapter 15](docs/15-error-codes.md) listing every error code, [Chapters 17](docs/17-tutorial-breakout.md) and [18](docs/18-tutorial-breakout-artwork.md) building a whole game twice, and [Chapters 20](docs/20-tutorial-galaga.md) and [21](docs/21-tutorial-galaga-enemies.md) building a C game that embeds the interpreter |
|
| [`docs/`](docs/README.md) | The guide: eighteen chapters, the language then each hardware area then a reference section for every verb and function, [Chapter 14](docs/14-architecture.md) on the interpreter's own architecture, [Chapter 15](docs/15-error-codes.md) listing every error code, and [Chapters 17](docs/17-tutorial-breakout.md) and [18](docs/18-tutorial-breakout-artwork.md) building a whole game twice |
|
||||||
| [`MAINTENANCE.md`](MAINTENANCE.md) | For contributors and maintainers: the documentation-example harness, the three test lists, mutation testing, error-code allocation, style |
|
| [`MAINTENANCE.md`](MAINTENANCE.md) | For contributors and maintainers: the documentation-example harness, the three test lists, mutation testing, error-code allocation, style |
|
||||||
| [`TODO.md`](TODO.md) | Outstanding defects, with file, line and consequence |
|
| [`TODO.md`](TODO.md) | Outstanding defects, with file, line and consequence |
|
||||||
| [`tests/language/README.md`](tests/language/README.md) | The editable language corpus and the rule for changing it |
|
| [`tests/language/README.md`](tests/language/README.md) | The editable language corpus and the rule for changing it |
|
||||||
|
|||||||
@@ -100,50 +100,6 @@ bounded run is usually inside a `FOR` or `GOSUB` body, and a variable created th
|
|||||||
dies when the body pops — silently, with the script reading it correctly right up until
|
dies when the body pops — silently, with the script reading it correctly right up until
|
||||||
it stops.
|
it stops.
|
||||||
|
|
||||||
## Calling a function every frame
|
|
||||||
|
|
||||||
`akbasic_runtime_call_function()` calls a `DEF` by name with values you already
|
|
||||||
hold — the entry point a game loop wants. A host that calls it repeatedly signs
|
|
||||||
up for three rules the one-shot examples never meet:
|
|
||||||
|
|
||||||
```c wrap=hostcalls
|
|
||||||
CATCH(errctx, akbasic_runtime_call_function(&SCRIPT, "THINK", argp, 1, &result));
|
|
||||||
/* ...consume the result... */
|
|
||||||
CATCH(errctx, akbasic_environment_zero(SCRIPT.environment));
|
|
||||||
```
|
|
||||||
|
|
||||||
1. **Reset the value scratch after every call, once the result is consumed.**
|
|
||||||
Each call parks its result in the caller environment's per-line scratch
|
|
||||||
(`AKBASIC_MAX_VALUES` slots), and a host calling in a loop never crosses the
|
|
||||||
line boundary that would reset it. Skip the `akbasic_environment_zero()` and
|
|
||||||
the pool drains — measured at under two frames of forty calls — after which
|
|
||||||
every call fails with `Maximum values per line reached`. The reset also
|
|
||||||
invalidates `result`, which is why it comes after the consumption.
|
|
||||||
2. **Force RUN mode once after the boot run.** A multi-line `DEF` body only
|
|
||||||
runs while the runtime is in RUN mode, and by the time a host can call, the
|
|
||||||
program that filed the definitions has ended. One
|
|
||||||
`akbasic_runtime_set_mode(&SCRIPT, AKBASIC_MODE_RUN)` after
|
|
||||||
`akbasic_runtime_run()` makes the bodies run, and the mode stays put because
|
|
||||||
nothing steps the runtime between calls. Issue #8 tracks making this
|
|
||||||
unnecessary.
|
|
||||||
3. **Revive after a script error, deliberately.** A BASIC-level error inside a
|
|
||||||
called body reports through the sink, answers a stale value, and latches:
|
|
||||||
the runtime leaves RUN mode and every later call does nothing. When your
|
|
||||||
policy is to absorb the error and keep calling — a game marking one actor
|
|
||||||
dumb rather than killing the frame — the revival is two calls:
|
|
||||||
`akbasic_runtime_clear_error()`, then `akbasic_runtime_set_mode(RUN)` again.
|
|
||||||
The latch is deliberate for *programs* — the first error ends a run, once,
|
|
||||||
with one line — so nothing clears it for you.
|
|
||||||
|
|
||||||
Do not pass structures as per-frame arguments. A structure or pointer parameter
|
|
||||||
spends a value-pool slot on every call and the pool never reclaims, so the
|
|
||||||
interface dies after about a thousand calls — issue #36 has the measurements.
|
|
||||||
Bind the instance once with `akbasic_host_bind()` and point it at each object
|
|
||||||
with `akbasic_host_rebind()` ([Chapter 16](16-structures.md)), which spends
|
|
||||||
nothing per call. The GALAGA tutorial ([Chapters 20](20-tutorial-galaga.md)
|
|
||||||
and [21](21-tutorial-galaga-enemies.md)) is this whole recipe as a working
|
|
||||||
game, forty calls a frame.
|
|
||||||
|
|
||||||
## Where the output goes
|
## Where the output goes
|
||||||
|
|
||||||
`PRINT` writes through an `akbasic_TextSink`, which is a record of function pointers plus
|
`PRINT` writes through an `akbasic_TextSink`, which is a record of function pointers plus
|
||||||
|
|||||||