aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: 86ef370d33bf8bb1afdf03e558e1fc50015c8c38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# Kitsune's Curse

This is an original game by [Juan J. Martinez](https://www.usebox.net/jjm/about/me/)
for the Amstrad CPC 464 or compatible.

Get the official releases from [Kitsune's Curse](https://www.usebox.net/jjm/kitsunes-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](https://www.mapeditor.org/) 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 source code of the game is licensed [GPL 3.0](gpl-3.0.txt).
* The assets are licensed [CC-BY-SA-NC](https://creativecommons.org/licenses/by-nc-sa/4.0/).

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