Delete the JRPG's collision too, and wall the map with proxies

cell_solid and feet_blocked go, and so does the prediction in the player's
movementlogicfunc. That prediction was only ever exact because the town has zero
gravity and zero drag -- v is t, so `x + tx * dt` is where the step lands. A map
with gravity would have had to fold ey in as well, which is the game
re-implementing the integrator. Resolution runs after the move now, so there is
nothing to predict.

The map's decoration layer carries `collidable`, which retires JRPG_LAYER_SOLID:
akgl_TilemapLayer has no name member, so the game and the map had to agree on an
index out of band and inserting a layer in Tiled broke it.

The edge of the world is not on any layer, so it is four static proxies covering
the outer ring plus a tile of overhang -- one pool slot per side instead of a
hundred solid tiles, and the first use of the static-proxy path in either
example. They carry LAYER_STATIC explicitly: shape_box defaults a shape to
LAYER_ACTOR, and a wall left on that layer is a wall everything walks through.

Only the player gets a shape. NPCs stand still and are spoken to; the follower
is a child snapped to its parent every step.

Verified against the old implementation with the demo script rewritten to hold
one direction: holding left into a building stops the player at x=122 with both,
and holding up into the map's edge stops them at y=-4 with both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
2026-08-02 07:46:19 -04:00
parent 98090e035c
commit 3690c2f311
5 changed files with 226 additions and 156 deletions

View File

@@ -19,6 +19,7 @@
#include <SDL3/SDL.h>
#include <akerror.h>
#include <akgl/actor.h>
#include <akgl/collision.h>
#include <akgl/types.h>
/*
@@ -49,16 +50,6 @@
/** @brief Point size the font is rasterized at. A size is baked into the handle. */
#define JRPG_FONT_SIZE 12
/**
* @brief Index of the tile layer this game treats as solid.
*
* An index, not a name, because akgl_TilemapLayer has no `name` member: the
* loader reads a layer's `id`, `type`, `opacity`, `visible`, offset and data,
* and drops the name Tiled wrote. A map cannot say "the layer called
* collision", so the game and the map agree on a number. See chapter 20.
*/
#define JRPG_LAYER_SOLID 1
/** @brief Longest line an NPC can say, terminator included. */
#define JRPG_TEXTBOX_MAX_TEXT 256
@@ -89,6 +80,15 @@ typedef struct {
* @return `NULL` on success, otherwise an error context owned by the caller.
*/
akerr_ErrorContext AKERR_NOIGNORE *jrpg_world_load(void);
/**
* @brief The collision world, owned by world.c.
*
* The physics backend resolves through it after every sub-move. Nothing else in
* the game touches it: the town is walls and people, and the walls are the
* map's own decoration layer plus four proxies around the edge.
*/
extern akgl_CollisionWorld jrpg_collision;
/**
* @brief Fix up the actors the map spawned, and attach the party member.
*
@@ -108,13 +108,13 @@ akerr_ErrorContext AKERR_NOIGNORE *jrpg_world_populate(void);
*/
akerr_ErrorContext AKERR_NOIGNORE *jrpg_camera_follow(void);
/**
* @brief The player's `movementlogicfunc`: the library default, plus walls.
* @brief The player's `movementlogicfunc`: the library default, plus the freeze.
*
* libakgl implements no collision at all -- akgl_physics_arcade_collide raises
* AKERR_API, akgl_physics_simulate never calls `collide`, and
* akgl_physics_arcade_move consults no tilemap -- so a game that wants a wall
* writes one here. Raises AKGL_ERR_LOGICINTERRUPT while the text box is open,
* which is the documented way to tell the simulator to skip an actor's tick.
* Walls are the library's now -- see chapter 15 -- so all this adds to
* akgl_actor_logic_movement is raising AKGL_ERR_LOGICINTERRUPT while the text
* box is open, which is the documented way to tell the simulator to skip an
* actor's tick. That also skips `collide`, since the CATCH around
* movementlogicfunc jumps past the whole rest of the step.
*
* @param obj The actor to compute acceleration for. Required, along with its
* `basechar`.