Compare commits
1
Commits
v0.1.1
..
e3cf5cdfa1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3cf5cdfa1 |
@@ -22,9 +22,7 @@ PPM 8bits channels | In-game `RRRGGGBB` value | Name
|
||||
`0x00_00_00` | `000_000_00` | Black
|
||||
`0x00_00_FF` | `000_000_11` | Blue
|
||||
`0x00_FF_00` | `000_111_00` | Green
|
||||
`0x99_50_00` | `010_001_00` | Brown
|
||||
`0x77_77_77` | `011_011_10` | Dark gray
|
||||
`0xB3_B3_B3` | `100_100_10` | Light gray
|
||||
`0x77_77_77` | `010_011_10` | Gray
|
||||
`0xFF_00_00` | `111_000_00` | Red
|
||||
`0xFF_00_FF` | `111_000_11` | Pink
|
||||
`0xFF_80_00` | `111_100_00` | Orange
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,45 +10,41 @@ if (Deno.args.length !== 0) {
|
||||
import { TextLineStream } from "jsr:@std/streams@1.1.0";
|
||||
|
||||
const asmFilePaths = [
|
||||
"src/consts/arch.asm",
|
||||
"src/consts/colors.asm",
|
||||
"src/consts/game.asm",
|
||||
"src/consts/keyboard.asm",
|
||||
"src/consts/map.asm",
|
||||
"src/consts/offsets.asm",
|
||||
"src/consts/scene.asm",
|
||||
"src/consts/screen.asm",
|
||||
"src/consts/sprites.asm",
|
||||
|
||||
"src/init.asm",
|
||||
"src/main.asm",
|
||||
|
||||
"src/lib/game.asm",
|
||||
"src/lib/drawing.asm",
|
||||
"src/lib/sprite_rotation.asm",
|
||||
];
|
||||
const reservedSpacePath = 'src/reserved_space.asm';
|
||||
|
||||
const instructionLength = 4; // in bytes
|
||||
const mapWidth = 15;
|
||||
const spriteWidth = 5;
|
||||
|
||||
const mapMatchingStrings = {
|
||||
0x00_00_00: '0', // Empty
|
||||
0xFF_00_00: '1', // Wall
|
||||
0xFF_FF_00: '2', // Coin
|
||||
0x00_00_00: 'U8 0', // Empty
|
||||
0xFF_00_00: 'U8 1', // Wall
|
||||
0xFF_FF_00: 'U8 2', // Coin
|
||||
};
|
||||
|
||||
const spritesMatchingStrings = {
|
||||
0x00_00_00: ' 0', // 0b000_000_00
|
||||
0x00_00_FF: ' 3', // 0b000_000_11
|
||||
0x00_FF_00: ' 28', // 0b000_111_00
|
||||
0x99_50_00: ' 68', // 0b010_001_00
|
||||
0x77_77_77: '110', // 0b011_011_10
|
||||
0xB3_B3_B3: '146', // 0b100_100_10
|
||||
0xFF_00_00: '224', // 0b111_000_00
|
||||
0xFF_00_FF: '227', // 0b111_000_11
|
||||
0xFF_80_00: '240', // 0b111_100_00
|
||||
0xFF_FF_00: '248', // 0b111_110_00
|
||||
0xFF_FF_FF: '255', // 0b111_111_11
|
||||
0x00_00_00: 'U8 0b000_000_00',
|
||||
0x00_00_FF: 'U8 0b000_000_11',
|
||||
0x00_FF_00: 'U8 0b000_111_00',
|
||||
0x77_77_77: 'U8 0b010_011_10',
|
||||
0xFF_00_00: 'U8 0b111_000_00',
|
||||
0xFF_00_FF: 'U8 0b111_000_11',
|
||||
0xFF_80_00: 'U8 0b111_100_00',
|
||||
0xFF_FF_00: 'U8 0b111_110_00',
|
||||
0xFF_FF_FF: 'U8 0b111_111_11',
|
||||
};
|
||||
|
||||
const mapPath = 'assets/map.ppm';
|
||||
@@ -89,27 +85,17 @@ for (const path of asmFilePaths) {
|
||||
const asmRawRepresentations = [];
|
||||
|
||||
const mapBytes = await getDataFromPPM(mapPath);
|
||||
asmRawRepresentations.push(bytesToAsmConstU8('map', mapBytes, mapMatchingStrings, mapWidth));
|
||||
asmRawRepresentations.push(byteToAsmRawData('map', mapBytes, mapMatchingStrings, mapWidth));
|
||||
|
||||
for (const [label, path] of Object.entries(spritesPath)) {
|
||||
const bytes = await getDataFromPPM(path);
|
||||
asmRawRepresentations.push(bytesToAsmConstU8(label, bytes, spritesMatchingStrings, spriteWidth));
|
||||
asmRawRepresentations.push(byteToAsmRawData(label, bytes, spritesMatchingStrings, spriteWidth));
|
||||
}
|
||||
|
||||
console.log(asmRawRepresentations.join("\n\n"));
|
||||
|
||||
console.log(`
|
||||
;
|
||||
; RESERVED RAM SPACE
|
||||
;
|
||||
`);
|
||||
|
||||
console.log(await Deno.readTextFile(reservedSpacePath))
|
||||
|
||||
/**
|
||||
* Return an ASM constant (U8) representation of the given data
|
||||
*
|
||||
* If needed, the representation will be padded to ensure the following code stays aligned on the instruction's length
|
||||
* Return an ASM representation of the given data
|
||||
*
|
||||
* @param label The label to write before the first raw data
|
||||
* @param data An array of bytes to interprete as 24 bits values
|
||||
@@ -118,18 +104,14 @@ console.log(await Deno.readTextFile(reservedSpacePath))
|
||||
*
|
||||
* @returns The ASM representation of that raw data, pre- and post-fixed by the given label
|
||||
*/
|
||||
function bytesToAsmConstU8(label: string, data: Uint8Array, matchingStrings: Record<number, string>, lineLength = 0): string {
|
||||
function byteToAsmRawData(label: string, data: Uint8Array, matchingStrings: Record<number, string>, lineLength = 0): string {
|
||||
lineLength ??= Infinity;
|
||||
|
||||
const stringValues = [];
|
||||
for (let i = 0; i < data.length - 2; i += 3) {
|
||||
const pixelValue = (data[i] << 16) + (data[i+1] << 8) + (data[i+2])
|
||||
if (!Object.hasOwn(matchingStrings, pixelValue)) {
|
||||
console.error(`No matching value defined for "${pixelValue.toString(2)}" (which is ${label}'s ${i} pixel)`);
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
stringValues.push(`U8 ${matchingStrings[pixelValue]}`);
|
||||
stringValues.push(matchingStrings[pixelValue]);
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
@@ -140,16 +122,10 @@ function bytesToAsmConstU8(label: string, data: Uint8Array, matchingStrings: Rec
|
||||
lines.push(stringValues.slice(i, i+lineLength).join("\t"));
|
||||
}
|
||||
}
|
||||
|
||||
const paddingBytes = [];
|
||||
for (let i = 0; i < stringValues.length % instructionLength; i++) {
|
||||
paddingBytes.push('U8 0');
|
||||
}
|
||||
const paddingString = paddingBytes.length === 0 ? '' : `\n${paddingBytes.join('\t')} ; padding to preserve ${8 * instructionLength} bits alignment`
|
||||
|
||||
return `${label}:
|
||||
${lines.join("\n")}
|
||||
${label}_end:${paddingString}`
|
||||
${label}_end:`
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,19 +9,7 @@ It thus should be able to run on the architecture and ISA the players have built
|
||||
The `main` branch contains a minimal implementation of the game.
|
||||
|
||||
The following branches contains variations, allowing comparisons of performances, visuals, gameplay, ...
|
||||
- `res-0-prerendered`:
|
||||
- `80x60` screen resolution
|
||||
- `5x5` tile resolution
|
||||
- initial screen prerendered
|
||||
- no sprite rotation
|
||||
- `res-2`:
|
||||
- `256x192` screen resolution
|
||||
- `17x17` tile resolution
|
||||
- `res-2-prerendered`:
|
||||
- `256x192` screen resolution
|
||||
- `17x17` tile resolution
|
||||
- initial screen prerendered
|
||||
- no sprite rotation
|
||||
- `res-2`: 256x192 screen resolution, 17x17 tile
|
||||
|
||||
## Build script
|
||||
|
||||
@@ -32,9 +20,6 @@ It also convert the PPM assets (map and sprites) into ASM raw values representat
|
||||
Usage example:
|
||||
```sh
|
||||
./build.ts > /path/to/the/game/schematics/architecture/Symfony/sandbox/main.asm
|
||||
|
||||
# script output can easily be piped to strip it off all comments, leading whitespace, double spaces and empty lines:
|
||||
./build.ts | sed -r 's/\s*;.*//' | sed -r 's/^\s+//' | sed -r 's/ +/ /' | sed -r '/^\s*$/d' > result.asm
|
||||
```
|
||||
|
||||
The script is build over Deno's API, so you'll need to have a `deno` binary in your PATH to use it that way.
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
pub const arch.instruction_size = 4
|
||||
pub const arch.instruction_increment_shift = 2
|
||||
@@ -0,0 +1,9 @@
|
||||
pub const colors.black = 0b000_000_00
|
||||
pub const colors.blue = 0b000_000_11
|
||||
pub const colors.green = 0b000_111_00
|
||||
pub const colors.gray = 0b010_011_10
|
||||
pub const colors.red = 0b111_000_00
|
||||
pub const colors.pink = 0b111_000_11
|
||||
pub const colors.orange = 0b111_100_00
|
||||
pub const colors.yellow = 0b111_110_00
|
||||
pub const colors.white = 0b111_111_11
|
||||
@@ -0,0 +1,16 @@
|
||||
;const free_ram_start = 8000
|
||||
|
||||
pub const offsets.screen = 8000 ; screen size: 80x60 = 4800
|
||||
|
||||
pub const offsets.tick_duration_high = 12800
|
||||
pub const offsets.tick_duration_low = 12804
|
||||
pub const offsets.next_tick_high = 12808
|
||||
pub const offsets.next_tick_low = 128012
|
||||
|
||||
|
||||
; State:
|
||||
; U8 x coord
|
||||
; U8 y coord
|
||||
; U8 direction
|
||||
pub const offsets.state_robot = 12816
|
||||
pub const offsets.state_ghost = 12819
|
||||
@@ -1,70 +0,0 @@
|
||||
;
|
||||
; Jump table initialisation
|
||||
;
|
||||
; r1: sprite id
|
||||
; r2: pointer address
|
||||
; r3: pointer value
|
||||
|
||||
; sprite_empty
|
||||
add r1, zr, sprites.empty
|
||||
lsl r2, r1, arch.instruction_increment_shift
|
||||
add r2, r2, ptr.textures_addresses
|
||||
add r3, zr, sprite_empty
|
||||
store_32 [r2], r3
|
||||
; sprite_unimplemented
|
||||
add r1, zr, sprites.unimplemented
|
||||
lsl r2, r1, arch.instruction_increment_shift
|
||||
add r2, r2, ptr.textures_addresses
|
||||
add r3, zr, sprite_unimplemented
|
||||
store_32 [r2], r3
|
||||
; sprite_wall_end
|
||||
add r1, zr, sprites.wall_end
|
||||
lsl r2, r1, arch.instruction_increment_shift
|
||||
add r2, r2, ptr.textures_addresses
|
||||
add r3, zr, sprite_wall_end
|
||||
store_32 [r2], r3
|
||||
; sprite_wall_straight
|
||||
add r1, zr, sprites.wall_straight
|
||||
lsl r2, r1, arch.instruction_increment_shift
|
||||
add r2, r2, ptr.textures_addresses
|
||||
add r3, zr, sprite_wall_straight
|
||||
store_32 [r2], r3
|
||||
; sprite_wall_corner
|
||||
add r1, zr, sprites.wall_corner
|
||||
lsl r2, r1, arch.instruction_increment_shift
|
||||
add r2, r2, ptr.textures_addresses
|
||||
add r3, zr, sprite_wall_corner
|
||||
store_32 [r2], r3
|
||||
; sprite_robot
|
||||
add r1, zr, sprites.robot
|
||||
lsl r2, r1, arch.instruction_increment_shift
|
||||
add r2, r2, ptr.textures_addresses
|
||||
add r3, zr, sprite_robot
|
||||
store_32 [r2], r3
|
||||
; sprite_ghost
|
||||
add r1, zr, sprites.ghost
|
||||
lsl r2, r1, arch.instruction_increment_shift
|
||||
add r2, r2, ptr.textures_addresses
|
||||
add r3, zr, sprite_ghost
|
||||
store_32 [r2], r3
|
||||
; sprite_coin
|
||||
add r1, zr, sprites.coin
|
||||
lsl r2, r1, arch.instruction_increment_shift
|
||||
add r2, r2, ptr.textures_addresses
|
||||
add r3, zr, sprite_coin
|
||||
store_32 [r2], r3
|
||||
|
||||
;
|
||||
; Screen initialization
|
||||
;
|
||||
|
||||
; r1 = screen parameter value
|
||||
mov r1, screen.mode_index
|
||||
screen r1, screen.mode_value
|
||||
|
||||
mov r1, screen.offset_index
|
||||
add r2, zr, ptr.screen
|
||||
screen r1, r2
|
||||
|
||||
mov r1, screen.resolution_index
|
||||
screen r1, screen.resolution_value
|
||||
+34
-7
@@ -77,7 +77,7 @@ get_tile_address:
|
||||
cmp r3, sprites.height
|
||||
jne get_tile_address_offset_per_row_loop
|
||||
|
||||
add r13, zr, ptr.screen
|
||||
mov r13, offsets.screen
|
||||
|
||||
mov r3, 0
|
||||
get_tile_address_margin_top_loop:
|
||||
@@ -120,21 +120,48 @@ get_tile_address:
|
||||
; r13: address
|
||||
|
||||
; Locals:
|
||||
; r2: pointer address
|
||||
; r2: jump address
|
||||
; */
|
||||
get_sprite_address:
|
||||
push r1
|
||||
push r2
|
||||
|
||||
cmp r1, sprites.max
|
||||
jbe get_sprite_address_ptr
|
||||
jbe get_sprite_address_jmp_table_init
|
||||
mov r1, sprites.unimplemented
|
||||
|
||||
get_sprite_address_ptr:
|
||||
lsl r2, r1, arch.instruction_increment_shift
|
||||
add r2, r2, ptr.textures_addresses
|
||||
load_32 r13, [r2]
|
||||
get_sprite_address_jmp_table_init:
|
||||
lsl r2, r1, 3 ; 2*instruction_size = *8 = <<3
|
||||
add r2, r2, get_sprite_address_jmp_table_start
|
||||
jmp r2
|
||||
|
||||
get_sprite_address_jmp_table_start:
|
||||
; sprite_empty
|
||||
add r13, zr, sprite_empty
|
||||
jmp get_sprite_address_return
|
||||
; sprite_unimplemented:
|
||||
add r13, zr, sprite_unimplemented
|
||||
jmp get_sprite_address_return
|
||||
; sprite_wall_end
|
||||
add r13, zr, sprite_wall_end
|
||||
jmp get_sprite_address_return
|
||||
; sprite_wall_straight
|
||||
add r13, zr, sprite_wall_straight
|
||||
jmp get_sprite_address_return
|
||||
; sprite_wall_corner
|
||||
add r13, zr, sprite_wall_corner
|
||||
jmp get_sprite_address_return
|
||||
; sprite_robot
|
||||
add r13, zr, sprite_robot
|
||||
jmp get_sprite_address_return
|
||||
; sprite_ghost
|
||||
add r13, zr, sprite_ghost
|
||||
jmp get_sprite_address_return
|
||||
; sprite_coin:
|
||||
add r13, zr, sprite_coin
|
||||
;jmp get_sprite_address_return
|
||||
|
||||
get_sprite_address_return:
|
||||
pop r2
|
||||
pop r1
|
||||
ret
|
||||
|
||||
+41
-22
@@ -1,6 +1,18 @@
|
||||
include consts/screen
|
||||
|
||||
include init
|
||||
;
|
||||
; Screen initialization
|
||||
;
|
||||
|
||||
; r1 = screen parameter value
|
||||
mov r1, screen.mode_index
|
||||
screen r1, screen.mode_value
|
||||
|
||||
mov r1, screen.offset_index
|
||||
screen r1, offsets.screen
|
||||
|
||||
mov r1, screen.resolution_index
|
||||
screen r1, screen.resolution_value
|
||||
|
||||
call drawing.draw_whole_map
|
||||
|
||||
@@ -15,7 +27,7 @@ const robot_initial_direction = game.direction_down
|
||||
mov r1, robot_initial_x
|
||||
mov r2, robot_initial_y
|
||||
mov r5, robot_initial_direction
|
||||
add r6, zr, ptr.state_robot
|
||||
mov r6, offsets.state_robot
|
||||
store_8 [r6], r1
|
||||
add r6, r6, 1
|
||||
store_8 [r6], r2
|
||||
@@ -34,7 +46,7 @@ const ghost_initial_direction = game.direction_down
|
||||
mov r1, ghost_initial_x
|
||||
mov r2, ghost_initial_y
|
||||
mov r5, ghost_initial_direction
|
||||
add r6, zr, ptr.state_ghost
|
||||
mov r6, offsets.state_ghost
|
||||
store_8 [r6], r1
|
||||
add r6, r6, 1
|
||||
store_8 [r6], r2
|
||||
@@ -48,10 +60,17 @@ call drawing.draw_sprite_at_map_coord
|
||||
;
|
||||
; Time initialization
|
||||
;
|
||||
add r1, zr, ptr.tick_duration_low
|
||||
load_32 r2, [r1]
|
||||
add r1, zr, ptr.tick_duration_high
|
||||
load_32 r3, [r1]
|
||||
mov r2, game.tick_duration_1
|
||||
movt r2, r2
|
||||
movb r2, game.tick_duration_0
|
||||
mov r1, offsets.tick_duration_low
|
||||
store_32 [r1], r2
|
||||
|
||||
mov r3, game.tick_duration_3
|
||||
movt r3, r3
|
||||
movb r3, game.tick_duration_2
|
||||
mov r1, offsets.tick_duration_high
|
||||
store_32 [r1], r3
|
||||
|
||||
time_0 r4
|
||||
time_1 r5
|
||||
@@ -62,9 +81,9 @@ jae init_store_next_tick
|
||||
add r5, r5, 1 ; overflow
|
||||
|
||||
init_store_next_tick:
|
||||
add r1, zr, ptr.next_tick_low
|
||||
mov r1, offsets.next_tick_low
|
||||
store_32 [r1], r4
|
||||
add r1, zr, ptr.next_tick_high
|
||||
mov r1, offsets.next_tick_high
|
||||
store_32 [r1], r5
|
||||
|
||||
game_loop:
|
||||
@@ -106,7 +125,7 @@ game_loop:
|
||||
; jmp game_loop_event_handling_end
|
||||
|
||||
game_loop_event_handling_store_direction:
|
||||
add r2, zr, ptr.state_robot
|
||||
add r2, zr, offsets.state_robot
|
||||
add r2, r2, 2
|
||||
store_8 [r2], r1
|
||||
|
||||
@@ -124,9 +143,9 @@ game_loop:
|
||||
time_0 r2
|
||||
time_1 r3
|
||||
|
||||
add r1, zr, ptr.next_tick_low
|
||||
mov r1, offsets.next_tick_low
|
||||
load_32 r4, [r1]
|
||||
add r1, zr, ptr.next_tick_high
|
||||
mov r1, offsets.next_tick_high
|
||||
load_32 r5, [r1]
|
||||
|
||||
cmp r3, r5
|
||||
@@ -136,9 +155,9 @@ game_loop:
|
||||
jb game_loop
|
||||
|
||||
update_next_tick:
|
||||
add r1, zr, ptr.tick_duration_low
|
||||
mov r1, offsets.tick_duration_low
|
||||
load_32 r2, [r1]
|
||||
add r1, zr, ptr.tick_duration_high
|
||||
mov r1, offsets.tick_duration_high
|
||||
load_32 r3, [r1]
|
||||
add r4, r4, r2
|
||||
add r5, r5, r3
|
||||
@@ -147,9 +166,9 @@ game_loop:
|
||||
add r5, r5, 1
|
||||
|
||||
store_next_tick:
|
||||
add r1, zr, ptr.next_tick_low
|
||||
mov r1, offsets.next_tick_low
|
||||
store_32 [r1], r4
|
||||
add r1, zr, ptr.next_tick_high
|
||||
mov r1, offsets.next_tick_high
|
||||
store_32 [r1], r5
|
||||
|
||||
|
||||
@@ -165,7 +184,7 @@ game_loop:
|
||||
; r7 = new position address
|
||||
; r8 = new position content
|
||||
; r9 = jump address
|
||||
add r1, zr, ptr.state_robot
|
||||
mov r1, offsets.state_robot
|
||||
load_8 r2, [r1]
|
||||
add r1, r1, 1
|
||||
load_8 r3, [r1]
|
||||
@@ -210,7 +229,7 @@ game_loop:
|
||||
cmp r8, map.tiles.wall
|
||||
jne game_loop_robot_movement_some
|
||||
; hit wall
|
||||
add r1, zr, ptr.state_robot
|
||||
mov r1, offsets.state_robot
|
||||
add r1, r1, 2
|
||||
mov r4, game.direction_still
|
||||
store_8 [r1], r4
|
||||
@@ -221,7 +240,7 @@ game_loop:
|
||||
mov r8, map.tiles.empty
|
||||
store_8 [r7], r8
|
||||
|
||||
add r1, zr, ptr.state_robot
|
||||
mov r1, offsets.state_robot
|
||||
store_8 [r1], r5
|
||||
add r1, r1, 1
|
||||
store_8 [r1], r6
|
||||
@@ -257,7 +276,7 @@ game_loop:
|
||||
; r9: new position address
|
||||
; r10: new position content
|
||||
; r11: jump address
|
||||
add r1, zr, ptr.state_ghost
|
||||
mov r1, offsets.state_ghost
|
||||
load_8 r2, [r1]
|
||||
add r1, r1, 1
|
||||
load_8 r3, [r1]
|
||||
@@ -289,7 +308,7 @@ game_loop:
|
||||
game_loop_ghost_movement_make_move:
|
||||
add r7, r2, r5
|
||||
add r8, r3, r6
|
||||
add r1, zr, ptr.state_ghost
|
||||
mov r1, offsets.state_ghost
|
||||
store_8 [r1], r7
|
||||
add r1, r1, 1
|
||||
store_8 [r1], r8
|
||||
@@ -336,7 +355,7 @@ game_loop:
|
||||
cmp r10, map.tiles.wall
|
||||
jne game_loop_ghost_movement_end
|
||||
; increment direction
|
||||
add r1, zr, ptr.state_ghost
|
||||
mov r1, offsets.state_ghost
|
||||
add r1, r1, 2
|
||||
load_8 r4, [r1]
|
||||
add r4, r4, 1
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
; Sprite addresses jump table
|
||||
pub ptr.textures_addresses:
|
||||
U32 0 ; empty
|
||||
U32 0 ; placeholder
|
||||
U32 0 ; wall_end
|
||||
U32 0 ; wall_straight
|
||||
U32 0 ; wall_corner
|
||||
U32 0 ; robot
|
||||
U32 0 ; ghost
|
||||
U32 0 ; coin
|
||||
|
||||
; 0.5 second = 500_000_000 nano second = 0x0000_0000_1DCD_6500
|
||||
pub ptr.tick_duration_high: U32 0x0000_0000
|
||||
pub ptr.tick_duration_low: U32 0x1DCD_6500
|
||||
|
||||
; 1 second = 1_000_000_000 nano second = 0x0000_0000_3B9A_CA00
|
||||
; pub ptr.tick_duration_high: U32 0x0000_0000
|
||||
; pub ptr.tick_duration_low: U32 0x3B9A_CA00
|
||||
|
||||
pub ptr.next_tick_high: U32 0
|
||||
pub ptr.next_tick_low: U32 0
|
||||
|
||||
pub ptr.state_robot:
|
||||
U8 0 ; x coord
|
||||
U8 0 ; y coord
|
||||
U8 0 ; direction
|
||||
U8 0 ; [padding]
|
||||
|
||||
pub ptr.state_ghost:
|
||||
U8 0 ; x coord
|
||||
U8 0 ; y coord
|
||||
U8 0 ; direction
|
||||
U8 0 ; [padding]
|
||||
|
||||
pub ptr.screen:
|
||||
Reference in New Issue
Block a user