aboutsummaryrefslogtreecommitdiff

Kitsune's Curse

This is an original game by Juan J. Martinez for the Amstrad CPC 464 or compatible.

Get the official releases from Kitsune's Curse.

The game is finished, no further development is planned.
The code has been open sourced in case the community finds it useful and helps making new games!

Requirements

  • A POSIX environment (Linux is perfect, Debian recommended)
  • gcc (C & C++), GNU make, cmake, Python 3
  • SDCC 4.0; later version may not work as SDCC changed the calling convention!

Building

Run make.

After a successful build, it should end with something like:

chksize 2816 ../build/main.map
***
      Max: 46080 bytes
  Current: 42774 bytes (490 bytes left)
***

To build and run on an emulator make cpcec (there are other targets).

Other targets

  • make clean to remove output of compilation and generated files
  • make cleanall to also remove the compiled tools

Technical details

This game uses an engine that tracks dirty tiles and minimises the amount of drawing on the screen. It is essentially the same engine used in "The Dawn of Kernel". See "Mini-buffer engine" for further info.

Memory Map:

Address Content
0x0100 mini-buffers
0x0b00 Game's code / ROM
0xa2fd Game's data / RAM
0xb215 Stack limit
0xc000 Video memory

(game's data and the stack limit may not be exact, see build/main.map for details once the game is built)

Mini-buffer engine

The game uses "mini-buffers" to build the scene based on dirty tiles.

Instead of tracking the areas of the screen that need redrawing between frames using a big buffer, when a part of the screen needs redrawing a small buffer is allocated and background and masked sprites are drawn to it. When the scene is finished, those mini-buffers are copied to the screen in a fast loop.

This reduces the memory requirements considerably, with some impact in the performance derived of the mini-buffer management. It can be a good engine for mid-size games targeting 64K that don't require a large amount of sprites on screen (this game supports up to 10 sprites of 8 * 24 pixels).

The memory area dedicated to the mini-buffers is calculated with this formula:

(TW * TH / 2) * (SPRITE_TH * 2 + 2) * MAX_SPRITES
  • TW: tile width in bytes (pixels / 2).
  • TH: tile height in pixels.
  • SPRITES_TH: sprites height in tiles (of TH pixels).
  • MAX_SPRITES: maximum number of sprites we may need to draw.

In "Kitsune's Curse" this is 2560 bytes (placed on 0x0100 before the game's code), which is very little memory (e.g. a back buffer by hardware would require 16384 bytes).

The dirty tiles are tracked with a tile map of the size of the playable screen (TMW * TMH; which is 20 * 20 or 160x160 pixels). Each entry on the map has a mini-buffer address that must be under 0x8000 because the most significant bit on the address is used as dirty flag, a pointer to the background tile, and the screen address where it should be drawn.

Then there are functions to manipulate the tile map, and those functions either validate (no need to draw) or invalidate tiles (the tiles are "dirty" and need to be drawn on the screen).

The tile map is drawn on screen with a call to update screen. At this point, all the mini-buffers are freed and available to start drawing the next frame.

This game updates at 16.6 FPS, so there are 3 full frames to deal with game logic update, prepare mini-buffers, and update the screen.

Useful flags

There are some defines to help troubleshoot issues:

  • DEBUG: show some debug information on screen (e.g. player checkpoints).
  • SPRITE_DEBUG: will use tile 14 to erase backgrounds providing a visual cue of how the erase is behaving.
  • FENCE_DEBUG: keep count of the used mini-buffers and boundaries when accessing several functions. On error, the screen border will change color (e.g. 0x4b when getting a tile off-bounds, 0x58 when run out of mini-buffers, etc), see splib.c for details.

Map structure

The game has 60 screens, using 7682 bytes (128.03 bytes per screen on average).

The map was designed using tiled exporting JSON and processed by a Python tool. See data/stage.json.

The binary data includes n maps, with each map:

  • 1 byte: map data length, not including flags (0 for empty map; no more data included)
  • 1 byte: flags
  • map length bytes: map data (4-bit per tile) H x W x n (4-bit per tile, compressed with apultra)
  • m bytes: entity data (0xff for end marker)

The map flags are:

  • 0-4: blocked (up, down, left, right)
  • 5-7: tileset

The converter tool expects layers Map and Entities, and some predefined entity type (on the object name).

By default each entity uses one sprite, unless a different weight is set for that type (e.g. the "link" entity type to connect screens doesn't use sprites). This information is used by the converter to validate that all screens are valid.

Entities can be persistent (tracking state; e.g. if a door was unlocked), otherwise they always respawn when the player enters the screen.

See tools/map.py for details.

Licence

The tools/libraries included that I don't own have their own copyright notices and license (some are public domain, others are open source).