Gaming & GX¶
A game is not a pile of pixels. It is a small world that changes one honest turn at a time.
GX is D/BASIC's shared game-model library. It gives a game a compact, portable vocabulary for scenes, entities, tilemaps, cameras, rectangle collision, and frame selection—without taking ownership of the window, renderer, controller, files, or sound system.
That boundary is purposeful. A tiny D/OS head, a browser, and a desktop can draw the same model very differently, while the rules that make the game your game remain the same.
Choose a path¶
Alpha availability
D/BASIC and GX are in alpha, so some engine capabilities described in this complete guide are not yet admitted by every compiler, runner, or target.
The games are the test suite¶
GX is not qualified by compiling one cheerful rectangle. Its game suite has three rings, each catching a different kind of mistake:
| Ring | Workload | What it proves |
|---|---|---|
| classic games | 96 complete BASIC game listings; 95 are admitted run cases and one deliberately non-terminating listing is refused from terminal-output comparison | game-shaped control flow, arrays, strings, numeric work, input, and deterministic execution remain sound beneath GX |
| GX sample games | the 17 programs shipped with the upstream GX project, from collision and tilemap probes through Santa and Escape | the port presents the engine vocabulary real GX programs use |
| the engine itself | 121,067 bytes of declarations and implementation, containing 152 public GX* procedure bodies in the current upstream revision |
imports, types, arrays, BYREF, large bytecode, paging, and library delivery survive a real engine rather than a toy fixture |
These are one qualification story, not three competing definitions of “the GX tests.” The classic games establish the D/BASIC floor; the GX sample games exercise the public game surface; and the engine source is the large-program stress test. The currently admitted core has passed its focused compile-and-execute gate, while the complete 17-game and 121 KB rings remain alpha qualification work. The documentation calls that boundary out so “GX works” always has an exact meaning.
Why GX exists¶
The first games were often wonderfully direct: move a number, test a hit, draw a thing. The trouble began when that logic quietly grew dependent on one display chip, one controller, and one memory map. Porting then meant rewriting the game instead of adapting the presentation.
GX keeps the good part—the directness—while separating three jobs:
| Layer | Owns |
|---|---|
| your application | rules, arrays, object model, turns, score, save decisions, and what a collision means |
| GX | checked, target-neutral operations over your scene/entity/tile/camera carriers |
| runner | input normalization, resource representation, rendering route, audio route, frame presentation, and capability admission |
The caller owns all mutable state. GX never keeps a secret world behind a handle. You allocate the carriers with DIM, can inspect them, decide when to serialize them, and choose when to render them. Invalid slots, dimensions, or undersized carriers leave the caller's state unchanged.
IMPORT gx
DIM Scene%(0 TO 7)
DIM Entities%(0 TO 19) ' two entity slots; each occupies ten words
DIM Tiles%(0 TO 319) ' a 20 by 16 tilemap
DIM Camera%(0 TO 1)
GXSceneOpen Scene%(), 20, 16
GXEntitySpawn Entities%(), 0, 16, 16, 8, 8, 1
GXEntityVelocity Entities%(), 0, 1, 0
GXTileSet Tiles%(), 20, 16, 4, 3, 7
GXSceneAdvance Scene%()
GXEntityStep Entities%(), 0
GXCameraFollow Camera%(), GXEntityX(Entities%(), 0), GXEntityY(Entities%(), 0), 160, 128, 80, 64
IMPORT gx names the governed shared GX module. It supplies game-model helpers; it does not create a game window or grant graphics, sound, file, or controller capability.
The current core carrier contracts¶
The currently admitted GX core uses rank-one INTEGER arrays as public carriers. This is intentionally plain: it works with D/BASIC's array rules, has no memory address hidden inside it, and gives the game an explicit allocation budget.
| Carrier | Required words | What GX stores |
|---|---|---|
Scene() |
8 | magic/version, world width and height, tick, active flag |
Entities() |
10 per slot | alive, position, size, velocity, animation frame/tick, kind |
Tiles() |
Columns * Rows |
one integer tile value per cell |
Camera() |
2 | camera X and Y |
The documented offsets are exported as constants such as GX_ENTITY_X_AT, GX_ENTITY_VX_AT, and GX_SCENE_TICK_AT. Most games should prefer the named procedures below. The constants are there for deliberate tools—an editor, inspector, or serialization format—not as an invitation to create a second incompatible engine.
Scene turns¶
GXSceneOpen Scene(), Width, Height
GXSceneAdvance Scene()
Ready% = GXSceneReady(Scene())
Words% = GXCarrierWords(Scene())
GXSceneOpen accepts only a positive world width and height and initializes the eight-word carrier. GXSceneAdvance increments the scene tick and wraps from 32767 to 0. GXSceneReady returns -1 only for a carrier carrying the current GX scene magic and version; it returns 0 otherwise.
A scene tick is game-model time, not a claim about real-world milliseconds. Your frame policy chooses how often to advance it.
Entities: small actors, explicit rules¶
An entity slot uses ten words. GXEntityCapacity tells you how many complete slots fit; any partial trailing words are not an entity. Slots begin at zero.
GXEntitySpawn Entities(), Slot, X, Y, Width, Height, Kind
GXEntityVelocity Entities(), Slot, VX, VY
GXEntityStep Entities(), Slot
X% = GXEntityX(Entities(), Slot)
Y% = GXEntityY(Entities(), Slot)
Capacity% = GXEntityCapacity(Entities())
Valid% = GXEntityValid(Entities(), Slot)
Spawning sets the entity alive, installs its geometry and Kind, and resets velocity and animation fields. GXEntityVelocity and GXEntityStep affect only valid, alive slots. A game decides how to remove an entity, apply gravity, clamp it to the world, or react to a hit; the core deliberately does not guess.
Collision is a question, not a policy¶
IF GXEntitiesOverlap(Entities%(), 0, 1) THEN
PRINT "THE HERO REACHED THE KEY"
END IF
IF GXRectOverlap(10, 10, 8, 8, 16, 10, 8, 8) THEN
PRINT "RECTANGLES TOUCH WITH AREA"
END IF
GXRectOverlap uses positive-width, positive-height rectangles and returns -1 only when they overlap with area; edge contact is not a collision. GXEntitiesOverlap first verifies both slots and that both entities are alive. The math forms far edges in LONG, so a valid entity near an integer edge cannot produce a wrapped, false hit.
What happens after -1 is the game: collect a key, bounce, lose health, open a conversation, or do nothing.
Tilemaps: a grid you own¶
GX's tilemap is a row-major integer array. X grows across columns; Y grows across rows. No storage is allocated by GXTileSet.
DIM Map%(0 TO 239)
GXTileSet Map%(), 20, 12, 3, 4, 9
Tile% = GXTileAt(Map%(), 20, 12, 3, 4)
At% = GXTileIndex(Map%(), 20, 12, 3, 4)
The map must have enough words for Columns * Rows. A nonpositive dimension, an out-of-range coordinate, or a map too small for the declared grid is rejected safely: GXTileIndex returns -1, GXTileAt returns 0, and GXTileSet leaves the array alone.
That makes tile IDs yours to interpret. 0 might mean empty sky in one game and a solid wall in another. A future drawing layer can map the same IDs to images, colours, animated tiles, or a deliberately lowered representation without changing the world data.
Camera: follow without falling off the map¶
DIM View%(0 TO 1)
GXCameraFollow View%(), HeroX%, HeroY%, WorldWidth%, WorldHeight%, ViewWidth%, ViewHeight%
INTENT GFX.FILL_RECT(GXEntityX(Entities%(), 0) - GXCameraX(View%()), GXEntityY(Entities%(), 0) - GXCameraY(View%()), 8, 8, -1)
GXCameraFollow centers the requested focus point, then clamps the camera to the declared world. If a viewport is larger than its world, that axis becomes zero rather than negative. GXCameraX and GXCameraY return zero for an undersized camera carrier.
The GFX.FILL_RECT line is intentionally ordinary D/BASIC: GX supplied the model position; the application chose how to render it. In a real window, place rendering inside WINDOW SUB DRAW() between the normal frame begin and present operations.
Animation: select; do not sleep¶
This selects frames 12, 13, 14, 15 in turn, holding each for six scene ticks. GXAnimationFrame(FirstFrame, FrameCount, Tick, TicksPerFrame) returns FirstFrame when the count, tick duration, or tick is invalid. It does not wait, allocate, or draw—ideal behavior for an event-driven program.
The entity carrier reserves frame and frame-tick fields (GX_ENTITY_FRAME_AT and GX_ENTITY_FRAME_TICK_AT) for a game that wants to retain that choice. The core does not impose an animation controller.
An object-shaped game model¶
GX is array-based by design, but it belongs comfortably in an object-oriented application. Put the game rules in a class; keep the compact carriers in application-owned storage; use methods to name meaning that GX cannot know.
IMPORT gx
DIM SHARED World%(0 TO 7)
DIM SHARED Actors%(0 TO 19)
DIM SHARED View%(0 TO 1)
CLASS GameSession
PUBLIC:
DECLARE SUB Reset ()
DECLARE SUB Tick ()
DECLARE FUNCTION HeroMetGuard () AS INTEGER
END CLASS
SUB GameSession.Reset ()
GXSceneOpen World%(), 160, 128
GXEntitySpawn Actors%(), 0, 20, 20, 8, 8, 1
GXEntitySpawn Actors%(), 1, 44, 20, 8, 8, 2
GXEntityVelocity Actors%(), 0, 1, 0
END SUB
SUB GameSession.Tick ()
GXSceneAdvance World%()
GXEntityStep Actors%(), 0
GXCameraFollow View%(), GXEntityX(Actors%(), 0), GXEntityY(Actors%(), 0), 160, 128, 80, 64
END SUB
FUNCTION GameSession.HeroMetGuard () AS INTEGER
GameSession.HeroMetGuard = GXEntitiesOverlap(Actors%(), 0, 1)
END FUNCTION
DIM Game AS GameSession
Game = NEW GameSession()
Game.Reset
Game.Tick
IF Game.HeroMetGuard() THEN PRINT "THE GUARD SAYS HELLO"
END
This is the useful split: GameSession owns the words “hero” and “guard”; GX owns checked rectangle math and carrier layout. In a larger program, a window event changes velocity, a frame turn calls Tick, and DRAW projects the resulting model.
Rendering, input, resources, and sound: the intended direction¶
GX is deliberately not a private rendering or audio stack. A game combines its engine model with the same portable facilities used by every other D/BASIC application:
| Need | Use now | Runner's job |
|---|---|---|
| drawing | GFX Intents in the window's draw turn |
upgrade to a rich composed route or lower to the admitted local head while preserving game geometry |
| keyboard / pointer | ON KEY SUB and ON POINTER SUB |
normalize hardware/browser input into D/BASIC events |
| packaged art | logical resources and an owning media adapter | decode, cache, scale, or choose a declared lower representation by resource identity |
| sound | admitted AUDIO Intents |
choose an honest native, mixed, tone, or bounded route; honor capability refusal |
| frame scheduling | window invalidation and a future ON FRAME direction |
choose an appropriate pacing route without making GX busy-wait |
Game turns stay bounded. Use the runner's admitted frame/event surface and never put an endless game loop inside WINDOW SUB DRAW().
This is the same adaptive-runner promise used elsewhere in D/BASIC: a browser can use high-density browser resources and presentation, a desktop can use richer local facilities, and a constrained route can lower detail or refuse an unavailable capability. Your GX arrays, collision outcomes, tile IDs, and game rules do not change to chase a machine.
For the surrounding contracts, see Windows & Events, Resources, Images & Sound, and Intents & Services.
Current GX core API at a glance¶
| Group | Admitted core operations |
|---|---|
| carrier and scene | GXCarrierWords, GXSceneReady, GXSceneOpen, GXSceneAdvance |
| entities | GXEntityCapacity, GXEntityBase, GXEntityValid, GXEntitySpawn, GXEntityVelocity, GXEntityStep, GXEntityX, GXEntityY |
| collision | GXRectOverlap, GXEntitiesOverlap |
| tiles | GXTileIndex, GXTileSet, GXTileAt |
| camera | GXCameraFollow, GXCameraX, GXCameraY |
| animation | GXAnimationFrame |
GXEntityBase is available for intentional inspection/tooling; normal game code should usually use the named entity functions. The public constants describe the current core layouts: GX_SCENE_*, GX_ENTITY_*, and GX_CAMERA_*.
A calm first-game checklist¶
- Allocate exact, visible carriers with
DIM. - Open one scene and spawn a small number of entities.
- Advance only from a bounded turn.
- Ask GX questions; let your game decide the consequence.
- Draw from state rather than modifying rules while painting.
- Treat resources, input, audio, and storage as portable services with real refusal paths.
- Keep the game playable when a visual route lowers; refuse clearly when its required capability is absent.