clean up game logic

This commit is contained in:
Lephenixnoir 2023-04-24 08:56:56 +02:00
parent 7c52c73716
commit 9c0287fbc4
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
6 changed files with 319 additions and 239 deletions

View File

@ -16,4 +16,4 @@ finish: 34
24: plane ARROW ARROW_+Z
26: plane DAMAGE HLINES_2_4
30-33: text "Look at you, all dizzy\njust from rolling a bit.\n\nFocus, I need some results!"
30-33: text "Look at you, all dizzy\njust from rolling a bit.\n\nGet ready, we need results!"

View File

@ -9,9 +9,8 @@ struct cd_raytrace_cmd
uint8_t shader_id;
uint8_t _[3];
/* Camera and world used for rendering */
struct camera const *camera;
struct world const *world;
/* Game being rendered (should not change during a frame) */
struct game const *game;
/* Current y value */
int y;
};
@ -24,7 +23,7 @@ void cd_raytrace_shader(void *uniforms0, void *cmd0, void *fragment)
int frag_height = uniforms;
int h = (frag_height > 112 - cmd->y) ? 112 - cmd->y : frag_height;
render_fragment(cmd->camera, cmd->world, (uint16_t *)fragment, cmd->y, h);
render_fragment(cmd->game, (uint16_t *)fragment, cmd->y, h);
cmd->y += h;
}
@ -34,14 +33,13 @@ static void register_shader(void)
CD_RAYTRACE_SHADER_ID = azrp_register_shader(cd_raytrace_shader);
}
void cd_raytrace(struct camera const *camera, struct world const *world)
void cd_raytrace(struct game const *game)
{
prof_enter(azrp_perf_cmdgen);
struct cd_raytrace_cmd cmd;
cmd.shader_id = CD_RAYTRACE_SHADER_ID;
cmd.camera = camera;
cmd.world = world;
cmd.game = game;
cmd.y = 0;
azrp_queue_command(&cmd, sizeof cmd, 0, azrp_frag_count);

View File

@ -124,6 +124,10 @@ struct level {
struct element_text *texts;
};
/* Total number of levels. */
int level_count(void);
/* Get a level by ID. */
struct level const *level_get(int id);
/* Check whether world coordinates x/z would collide with an object plane of
the provided shape. */
bool level_plane_collides(num16 x, num16 z, uint32_t shape);
@ -132,32 +136,65 @@ bool level_plane_collides(num16 x, num16 z, uint32_t shape);
// Dynamic world information
//---
struct world {
struct game {
/* Camera (including player position) */
struct camera *camera;
/* Neon cycle specification - independent from level */
num16 neon_position;
num16 neon_period;
/* Current objects */
num depth;
struct element_mirror *mirror;
struct element_plane *plane;
struct element_text *text;
/* Current level and its level number. When level is NULL and/or level_id is
negative, we are in the main menu. */
struct level const *level;
int level_id;
/* Current position within level's element arrays */
int mirror_index;
int plane_index;
int text_index;
/* Corresponding elements (these are the only active elements) */
struct element_mirror *mirror;
struct element_plane *plane;
struct element_text *text;
/* Distance traversed within the current level */
num depth;
/* Number of blank frames to show (for death transitions) */
int blank_frames;
/* Cursor position within the main menu */
int menu_cursor;
};
/* Check whether we are in the main menu. */
bool game_in_menu(struct game const *game);
/* Transition to the main menu. */
void game_load_menu(struct game *game);
/* Transition to a level. */
void game_load_level(struct game *game, int index, int blank_frames);
/* Restart current level. */
void game_restart_level(struct game *g, int blank_frames);
/* Go the previous level (or menu if currently at the first level). */
void game_prev_level_or_menu(struct game *g);
/* Go the next level (or menu if currently at the last level). */
void game_next_level_or_menu(struct game *g);
/* Return the plane currently in collision with the player, if any, or NULL. */
const struct element_plane *game_plane_collision(struct game const *g);
/* Advancee in level by unloading passed elements and loading incoming ones.
Check game_plane_collision() before calling this function. */
void game_advance_in_level(struct game *g);
//---
// Raytracing
//---
/* Core function. Renders an image of the provided world through the provided
camera. Renders lines [y_start ... y_start+y_height) on the specified VRAM
fragment. */
void render_fragment(struct camera const *camera, struct world const *world,
uint16_t *fragment, int y_start, int y_height);
/* Core function. Renders an image of the provided game; renders lines
[y_start ... y_start+y_height) on the specified VRAM fragment. */
void render_fragment(struct game const *game, uint16_t *fragment,
int y_start, int y_height);
/* Azur shader wrapping the raytracing function. */
void cd_raytrace(struct camera const *camera, struct world const *world);
void cd_raytrace(struct game const *game);
/* Shader configuration function, must be called at initialization and after
any scale change. */
void cd_raytrace_configure(void);

View File

@ -1,5 +1,39 @@
/* Functions for game-specific mechanics. */
#include "chaos-drop.h"
#include <azur/azur.h>
#ifdef AZUR_TOOLKIT_GINT
extern struct level level_1_1, level_1_2, level_1_3;
extern struct level level_2_1, level_2_2, level_2_3;
extern struct level level_3_1, level_3_2, level_3_3;
static struct level const *ALL_LEVELS[] = {
&level_1_1, &level_1_2, &level_1_3,
&level_2_1, &level_2_2, &level_2_3,
};
#else
/* TODO: PC/SDL build: Embed levels as in the add-in */
static struct level const lv_test = {
.name = "<Test>",
.finish = num(1024),
.mirror_count = 1,
.mirrors = (struct element_mirror[]){
{ .begin = 256, .end = 768 },
},
.plane_count = 2,
.planes = (struct element_plane[]){
{ .y = 384, .shape = 0b01010'10101'01010'10101'01010,
.type = ELEMENT_PLANE_DAMAGE, .data = 0 },
{ .y = 384+64, .shape = 0b11111'10001'10001'10001'11111,
.type = ELEMENT_PLANE_DAMAGE, .data = 0 },
},
.text_count = 1,
.texts = (struct element_text[]){
{ .begin = 32, .end = 256,
.str = "Red stuff bad!\nDon't touch the red stuff!", },
},
};
static struct level const *ALL_LEVELS[] = { &lv_test };
#endif
void camera_set_fov(struct camera *camera, float fov_degrees)
{
@ -113,6 +147,16 @@ void camera_update_angles(struct camera *camera)
camera->up = c2w * vec3(0, 0, 1);
}
int level_count(void)
{
return sizeof ALL_LEVELS / sizeof ALL_LEVELS[0];
}
const struct level *level_get(int id)
{
return (id >= 0 && id < level_count()) ? ALL_LEVELS[id] : NULL;
}
bool level_plane_collides(num16 x, num16 z, uint32_t shape)
{
int cx = ((x + num16(WORLD_SIZE)) * num16(5.0 / 2 / WORLD_SIZE)).ifloor();
@ -121,3 +165,96 @@ bool level_plane_collides(num16 x, num16 z, uint32_t shape)
return (unsigned)cx < 5 && (unsigned)cz < 5
&& ((shape >> (5*cz + 4-cx)) & 1);
}
bool game_in_menu(struct game const *g)
{
return !g->level;
}
static void game_reset_level_data(struct game *g)
{
g->level = NULL;
g->level_id = -1;
g->mirror_index = 0;
g->plane_index = 0;
g->text_index = 0;
g->mirror = NULL;
g->plane = NULL;
g->text = NULL;
g->depth = 0;
}
void game_load_menu(struct game *g)
{
game_reset_level_data(g);
g->level = NULL;
g->level_id = -1;
}
void game_load_level(struct game *g, int index, int blank_frames)
{
struct level const *level = level_get(index);
if(!level)
return;
game_reset_level_data(g);
g->level = level;
g->level_id = index;
g->blank_frames = blank_frames;
}
void game_restart_level(struct game *g, int blank_frames)
{
if(!game_in_menu(g))
game_load_level(g, g->level_id, blank_frames);
}
void game_prev_level_or_menu(struct game *g)
{
if(g->level_id > 0)
game_load_level(g, g->level_id - 1, 0);
else
game_load_menu(g);
}
void game_next_level_or_menu(struct game *g)
{
if(g->level_id >= 0 && g->level_id + 1 < level_count())
game_load_level(g, g->level_id + 1, 0);
else
game_load_menu(g);
}
const struct element_plane *game_plane_collision(struct game const *g)
{
return g->plane && g->depth > g->plane->y - num(4) ? g->plane : NULL;
}
void game_advance_in_level(struct game *g)
{
struct level const *level = g->level;
/* Remove old elements */
if(g->mirror && g->depth > g->mirror->end)
g->mirror = NULL;
if(game_plane_collision(g))
g->plane = NULL;
if(g->text && g->depth > g->text->end)
g->text = NULL;
/* Load incoming elements */
if(!g->mirror && g->mirror_index < level->mirror_count
&& level->mirrors[g->mirror_index].begin < g->depth + num(64)) {
g->mirror = &level->mirrors[g->mirror_index++];
}
if(!g->plane && g->plane_index < level->plane_count
&& level->planes[g->plane_index].y < g->depth + num(64)) {
g->plane = &level->planes[g->plane_index++];
}
if(!g->text && g->text_index < level->text_count
&& level->texts[g->text_index].begin < g->depth + num(64)) {
g->text = &level->texts[g->text_index++];
}
}

View File

@ -282,190 +282,105 @@ void render_centered_text(char const *str)
}
}
static struct camera *camera = NULL;
static struct world *world = NULL;
static int reset_frames = 0;
static bool debug = false;
static bool menu_active = true;
static int menu_cursor = 0;
#ifdef AZUR_TOOLKIT_GINT
extern struct level level_1_1, level_1_2, level_1_3;
extern struct level level_2_1, level_2_2, level_2_3;
extern struct level level_3_1, level_3_2, level_3_3;
static struct level const *levels[] = {
&level_1_1, &level_1_2, &level_1_3,
&level_2_1, &level_2_2, &level_2_3,
};
#else
/* Basic basic level */
static struct level const lv_test = {
.name = "<Test>",
.finish = num(1024),
.mirror_count = 1,
.mirrors = (struct element_mirror[]){
{ .begin = 256, .end = 768 },
},
.plane_count = 2,
.planes = (struct element_plane[]){
{ .y = 384, .shape = 0b01010'10101'01010'10101'01010,
.type = ELEMENT_PLANE_DAMAGE, .data = 0 },
{ .y = 384+64, .shape = 0b11111'10001'10001'10001'11111,
.type = ELEMENT_PLANE_DAMAGE, .data = 0 },
},
.text_count = 1,
.texts = (struct element_text[]){
{ .begin = 32, .end = 256,
.str = "Red stuff bad!\nDon't touch the red stuff!", },
},
};
static struct level const *levels[] = { &lv_test };
#endif
#define LEVEL_COUNT ((int)(sizeof levels / sizeof levels[0]))
static int current_level = 0;
void load_level_number(int number)
void game_render(struct game *g)
{
if(number < 0)
number = 0;
if(number > LEVEL_COUNT)
number = LEVEL_COUNT - 1;
current_level = number;
}
void world_reset(struct world *world)
{
world->neon_position = 0;
world->depth = 0;
world->neon_period = 64;
world->mirror = NULL;
world->plane = NULL;
world->text = NULL;
world->mirror_index = 0;
world->plane_index = 0;
world->text_index = 0;
}
void world_level_transition(struct world *world)
{
world->depth = world->depth % num(world->neon_period);
world->mirror = NULL;
world->plane = NULL;
world->text = NULL;
world->mirror_index = 0;
world->plane_index = 0;
world->text_index = 0;
}
void render(void)
{
struct level const *level = levels[current_level];
struct level const *level = g->level;
#ifdef AZUR_TOOLKIT_GINT
extern image_t img_title, img_cursor;
azrp_perf_clear();
if(menu_active) {
cd_raytrace(camera, world);
if(g->blank_frames > 0) {
g->blank_frames--;
azrp_clear(0xffff);
}
else if(game_in_menu(g)) {
cd_raytrace(g);
azrp_image((azrp_width - img_title.width) / 2, 4, &img_title);
for(int row = 0; row < 2; row++)
for(int col = 0; col < 3; col++) {
int i = 3 * row + col;
if(i >= LEVEL_COUNT)
if(i >= level_count())
continue;
int x = azrp_width / 2 + 40 * (col - 1);
int y = 55 + 20 * row;
int l = strlen(levels[i]->name);
int width = text_size(levels[i]->name, l);
char const *name = level_get(i)->name;
int l = strlen(name);
int width = text_size(name, l);
if(menu_cursor == i)
if(g->menu_cursor == i)
azrp_image(x - width/2 - 6, y-2, &img_cursor);
draw_text(x - width/2, y, levels[i]->name, l);
draw_text(x - width/2, y, name, l);
}
}
else if(reset_frames > 0) {
reset_frames--;
azrp_clear(0xffff);
}
else {
cd_raytrace(camera, world);
if(world->text)
render_centered_text(world->text->str);
cd_raytrace(g);
if(g->text)
render_centered_text(g->text->str);
draw_text(1, 1, level->name, strlen(level->name));
}
platform_render();
if(debug) {
drect(0, DHEIGHT-20, DWIDTH-1, DHEIGHT-1, C_WHITE);
dprint(4, 209, C_BLACK, "render:%4d+%4dus",
prof_time(azrp_perf_render) - prof_time(azrp_perf_r61524),
prof_time(azrp_perf_r61524));
r61524_display(gint_vram, DHEIGHT-20, 20, R61524_DMA_WAIT);
}
#else
if(reset_frames > 0) {
reset_frames--;
if(g->blank_frames > 0) {
g->blank_frames--;
memset(vram, 0xff, sizeof vram);
}
else {
render_fragment(camera, world, vram, 0, VHEIGHT);
if(world->text)
render_centered_text(world->text->str);
render_fragment(g, vram, 0, VHEIGHT);
if(g->text)
render_centered_text(g->text->str);
draw_text(1, 1, level->name, strlen(level->name));
}
platform_render();
#endif
}
int update(void)
int game_update(struct game *g, bool *debug)
{
struct input input = {};
if(platform_update(&input))
return 1;
struct camera *camera = g->camera;
bool menu = game_in_menu(g);
/* Debug controls */
if(input.OPTN)
debug = !debug;
if(!menu_active && input.RESET_LEVEL) {
world_reset(world);
reset_frames = 2;
*debug = !*debug;
if(!menu && input.RESET_LEVEL) {
game_restart_level(g, 2);
return 0;
}
if(!menu_active && input.NEXT_LEVEL && current_level < LEVEL_COUNT-1) {
world_level_transition(world);
load_level_number(current_level+1);
reset_frames = 2;
if(!menu && input.NEXT_LEVEL) {
game_next_level_or_menu(g);
g->blank_frames = 2;
return 0;
}
if(!menu_active && input.PREV_LEVEL && current_level > 0) {
world_level_transition(world);
load_level_number(current_level-1);
reset_frames = 2;
if(!menu && input.PREV_LEVEL) {
game_prev_level_or_menu(g);
g->blank_frames = 2;
return 0;
}
if(reset_frames > 0)
if(g->blank_frames > 0)
return 0;
if(menu_active) {
if(input.left_trigger && menu_cursor > 0) {
menu_cursor--;
if(menu) {
if(input.left_trigger && g->menu_cursor > 0) {
g->menu_cursor--;
}
if(input.right_trigger && menu_cursor + 1 < LEVEL_COUNT) {
menu_cursor++;
if(input.right_trigger && g->menu_cursor + 1 < level_count()) {
g->menu_cursor++;
}
if(input.up_trigger && menu_cursor >= 3) {
menu_cursor -= 3;
if(input.up_trigger && g->menu_cursor >= 3) {
g->menu_cursor -= 3;
}
if(input.down_trigger && menu_cursor + 3 < LEVEL_COUNT) {
menu_cursor += 3;
if(input.down_trigger && g->menu_cursor + 3 < level_count()) {
g->menu_cursor += 3;
}
if(input.enter) {
world_level_transition(world);
load_level_number(menu_cursor);
menu_active = false;
game_load_level(g, g->menu_cursor, 0);
menu = game_in_menu(g);
}
}
@ -477,7 +392,7 @@ int update(void)
static num const fall_speed = 2;
static float const barrel_snap = 0.5;
if(!menu_active) {
if(!menu) {
if(input.left) {
camera->pos -= mv_speed * camera->right;
camera->yaw = fmaxf(camera->yaw - rot_speed, -rot_max);
@ -516,88 +431,81 @@ int update(void)
camera_update_angles(camera);
}
world->depth += fall_speed;
world->neon_position += world->neon_period - num16(fall_speed);
world->neon_position %= world->neon_period;
g->depth += fall_speed;
g->neon_position += g->neon_period - num16(fall_speed);
g->neon_position %= g->neon_period;
if(menu_active)
if(menu)
return 0;
// Remove old world elements
// Apply collision with plane
struct world *w = world;
if(w->mirror && w->depth > w->mirror->end)
w->mirror = NULL;
if(w->plane && w->depth > w->plane->y - num(4)) {
/* Apply plane effect */
if((w->plane->type == ELEMENT_PLANE_DAMAGE ||
w->plane->type == ELEMENT_PLANE_INVIS)
&& level_plane_collides(num16(camera->pos.x), num16(camera->pos.z),
w->plane->shape))
{
world_reset(w);
reset_frames = 2;
}
else if(w->plane->type == ELEMENT_PLANE_ARROW &&
camera->roll_quadrant != w->plane->data) {
world_reset(w);
reset_frames = 2;
}
w->plane = NULL;
struct element_plane const *p = game_plane_collision(g);
if((p->type == ELEMENT_PLANE_DAMAGE || p->type == ELEMENT_PLANE_INVIS)
&& level_plane_collides(num16(camera->pos.x), num16(camera->pos.z),
p->shape))
{
game_restart_level(g, 2);
}
if(w->text && w->depth > w->text->end)
w->text = NULL;
else if(p->type == ELEMENT_PLANE_ARROW && camera->roll_quadrant != p->data)
game_restart_level(g, 2);
// Load incoming world elements
// Load incoming level elements
struct level const *level = levels[current_level];
game_advance_in_level(g);
if(!w->mirror && w->mirror_index < level->mirror_count
&& level->mirrors[w->mirror_index].begin < w->depth + num(64)) {
w->mirror = &level->mirrors[w->mirror_index++];
}
if(!w->plane && w->plane_index < level->plane_count
&& level->planes[w->plane_index].y < w->depth + num(64)) {
w->plane = &level->planes[w->plane_index++];
}
if(!w->text && w->text_index < level->text_count
&& level->texts[w->text_index].begin < w->depth + num(64)) {
w->text = &level->texts[w->text_index++];
}
// End of level
if(!w->mirror && !w->plane && !w->text && w->depth >= level->finish
&& !reset_frames) {
if(current_level >= LEVEL_COUNT-1) {
world_level_transition(w);
menu_active = true;
}
else {
world_level_transition(w);
load_level_number(current_level + 1);
}
if(!g->mirror && !g->plane && !g->text && g->depth >= g->level->finish
&& !g->blank_frames) {
game_next_level_or_menu(g);
}
return 0;
}
//---
static struct game *game = NULL;
static bool debug = false;
void render(void)
{
game_render(game);
platform_render();
#ifdef AZUR_TOOLKIT_GINT
if(debug) {
drect(0, DHEIGHT-20, DWIDTH-1, DHEIGHT-1, C_WHITE);
dprint(4, 209, C_BLACK, "render:%4d+%4dus",
prof_time(azrp_perf_render) - prof_time(azrp_perf_r61524),
prof_time(azrp_perf_r61524));
r61524_display(gint_vram, DHEIGHT-20, 20, R61524_DMA_WAIT);
}
#endif
}
int update(void)
{
return game_update(game, &debug);
}
int main(void)
{
if(azur_init("Chaos Drop!", 3*VWIDTH, 3*VHEIGHT))
return 1;
struct camera c = {};
camera = &c;
struct camera camera = {};
/* TODO: Why do I need such a low FOV?! */
camera_set_fov(camera, 80.0);
camera_update_angles(camera);
camera_set_fov(&camera, 80.0);
camera_update_angles(&camera);
struct world w = {};
load_level_number(0);
world_reset(&w);
world = &w;
struct game g = {};
g.camera = &camera;
g.menu_cursor = 0;
g.blank_frames = 0;
g.neon_position = 0;
g.neon_period = 64;
game_load_menu(&g);
game = &g;
init();
int rc = azur_main_loop(render, 30, update, -1, AZUR_MAIN_LOOP_TIED);

View File

@ -32,7 +32,7 @@ svec3 svec3_of_vec3(vec3 v)
the collision point in `*t` and the full-precision y-coordinate of that
point in `*collision_y`. Returns the identifier of the object type hit by
the ray. If `secondary` is true, invisible planes are not ignored. */
static int cast_ray(struct world const *world, svec3 const &origin,
static int cast_ray(struct game const *g, svec3 const &origin,
svec3 const &rayDir, num16 *t, num *collision_y, bool secondary)
{
/* We directly compute intersections between rays and walls by exploiting
@ -102,18 +102,18 @@ static int cast_ray(struct world const *world, svec3 const &origin,
/* Determine if there is an intersection with the object plane */
if(__builtin_expect(
world->plane
&& (*collision_y > world->plane->y - world->depth)
&& (world->plane->type != ELEMENT_PLANE_INVIS || secondary),
g->plane
&& (*collision_y > g->plane->y - g->depth)
&& (g->plane->type != ELEMENT_PLANE_INVIS || secondary),
0)) {
num16 plane_y = num16(world->plane->y - world->depth);
num16 plane_y = num16(g->plane->y - g->depth);
num16 plane_t = (plane_y - origin.y) / rayDir.y;
num16 plane_x = origin.x + plane_t * rayDir.x;
num16 plane_z = origin.z + plane_t * rayDir.z;
if(level_plane_collides(plane_x, plane_z, world->plane->shape)) {
if(level_plane_collides(plane_x, plane_z, g->plane->shape)) {
*t = plane_t;
*collision_y = world->plane->y - world->depth;
*collision_y = g->plane->y - g->depth;
return OBJECT_PLANE;
}
}
@ -121,9 +121,10 @@ static int cast_ray(struct world const *world, svec3 const &origin,
return hit;
}
void render_fragment(struct camera const *camera, struct world const *world,
uint16_t *fragment, int y_start, int y_height)
void render_fragment(struct game const *g, uint16_t *fragment,
int y_start, int y_height)
{
struct camera const *camera = g->camera;
svec3 origin = svec3_of_vec3(camera->pos);
svec3 forward = svec3_of_vec3(camera->forward);
svec3 right = svec3_of_vec3(camera->right);
@ -146,11 +147,10 @@ void render_fragment(struct camera const *camera, struct world const *world,
for(int x = 0; x < VWIDTH; x++) {
num16 t;
num coll_y;
int obj = cast_ray(world, origin, rayDir, &t, &coll_y, false);
int obj = cast_ray(g, origin, rayDir, &t, &coll_y, false);
bool darken = false;
if(__builtin_expect(
world->mirror && (obj == LEFT || obj == RIGHT), 0)) {
if(__builtin_expect(g->mirror && (obj==LEFT || obj==RIGHT), 0)) {
svec3 collision;
collision.z = origin.z + t * rayDir.z;
@ -158,37 +158,37 @@ void render_fragment(struct camera const *camera, struct world const *world,
bool ok_z = collision.z >= num16(-WORLD_SIZE / 2)
&& collision.z < num16(WORLD_SIZE / 2);
/* Mirror also does not go on forever */
bool ok_y = world->mirror
&& coll_y >= (world->mirror->begin - world->depth)
&& coll_y < (world->mirror->end - world->depth);
bool ok_y = g->mirror
&& coll_y >= (g->mirror->begin - g->depth)
&& coll_y < (g->mirror->end - g->depth);
if(ok_z && ok_y) {
collision.x = origin.x + t * rayDir.x;
collision.y = num16(coll_y);
rayDir.x = -rayDir.x;
obj = cast_ray(world, collision, rayDir, &t, &coll_y,true);
obj = cast_ray(g, collision, rayDir, &t, &coll_y,true);
darken = true;
rayDir.x = -rayDir.x;
}
}
uint16_t color = object_colors[
obj + (obj == OBJECT_PLANE ? world->plane->type : 0)];
obj + (obj == OBJECT_PLANE ? g->plane->type : 0)];
/* Don't show neons that are too far to avoid flickering */
if(coll_y < 64 && obj != OBJECT_PLANE) {
num16 neon_pos = world->neon_position;
num16 neon_pos = g->neon_position;
if(obj == TOP || obj == BOTTOM)
neon_pos += world->neon_period * num16(0.5);
neon_pos += g->neon_period * num16(0.5);
num16 neon = num16(coll_y) - neon_pos;
neon.v = neon.v & (world->neon_period.v - 1);
neon.v = neon.v & (g->neon_period.v - 1);
/* Also make neons larger when they're far to further avoid
flickering */
num16 neon_size = 1;
if(coll_y > 20)
neon_size += ((num16(coll_y)-num16(20)) * num16(1.0/4));
if(neon <= neon_size)
color = world->text ? RGB(15,15,15) : 0xffff;
color = g->text ? RGB(15,15,15) : 0xffff;
}
else if(__builtin_expect(coll_y >= 64, 0)) {
if(coll_y > 128)