code wiki / (root) / nx_game_engine_compose_test.nx

nx_game_engine_compose_test.nx source

↩ module page · 131 lines · 5408 B

1// nx_game_engine_compose_test.nx -- the full game-loop scaffold. 2// 3// All 4 new primitives composed with the prior Minecraft-MCU 4// substrate. Realistic game-loop shape: 5// 1. world_seed generates 16x16x8 terrain from seed=42 6// 2. font + 1bpp framebuffer prepared 7// 3. raycast + dither pattern renders the world to fb 8// 4. text_render overlays "Z42" coordinate 9// 5. input drives camera movement 10// 6. q14_math normalizes camera direction 11// 12// This proves the substrate-side game engine works end-to-end on a 13// hardware-floor that maps cleanly to ESP32-class silicon. 14 15import "nx_syscalls.nx" 16import "nx_tier.nx" 17import "nx_palette.nx" 18import "nx_tissue.nx" 19import "nx_raycast_voxel.nx" 20import "nx_fb_1bpp.nx" 21import "nx_input_gpio.nx" 22import "nx_q14_math.nx" 23import "nx_font_1bpp.nx" 24import "nx_text_render.nx" 25import "nx_world_seed.nx" 26 27func main() -> i64 { 28 // ===== Step 1: deterministic world generation =================== 29 let world: *NxTissue = nx_tissue_new(16, 16, 8, NX_BPP_3) 30 let cells_filled: nx_int = nx_world_seed_generate(world, 42) 31 if cells_filled <= 0 { return 1 } 32 // Stone floor at z=0 should be all stone 33 if nx_tissue_get(world, 0, 0, 0) != NX_WS_PAL_STONE { return 2 } 34 35 // Verify determinism: regenerate, byte-identical 36 let world2: *NxTissue = nx_tissue_new(16, 16, 8, NX_BPP_3) 37 nx_world_seed_generate(world2, 42) 38 if nx_tissue_get(world, 8, 8, 4) != nx_tissue_get(world2, 8, 8, 4) { return 3 } 39 40 // ===== Step 2: framebuffer + font =============================== 41 let fb: *NxFb1bpp = nx_fb_1bpp_new(32, 16) 42 nx_fb_1bpp_clear(fb) 43 44 // Tiny 3-char "Z42" font (5 chars including space + minus) 45 let glyph_data: *u8 = (sys_mmap(64)) as *u8 46 // Just need glyphs for '4', '2', 'Z' = decimal 52, 50, 90 47 // Easiest: use base_char='2' and glyph_count=89 won't fit... 48 // For test, base_char='0', glyph_count=43 (0..Z range 48..90) 49 // Each glyph: 3 cols x 1 byte = 3 bytes. 43 * 3 = 129 bytes data buffer 50 let big_data: *u8 = (sys_mmap(256)) as *u8 51 // Initialize the digits 0-9 ('0'..'9' = 48..57) with stub patterns 52 var i: nx_size = 0 53 while i < 129 { 54 big_data[i] = (i & 31) as u8 // arbitrary distinct patterns 55 i = i + 1 56 } 57 let font: *NxFont = nx_font_new(big_data, 3, 5, 43, 48) 58 if (font as i64) == 0 { return 4 } 59 if nx_font_has_char(font, 50) != 1 { return 5 } // '2' 60 if nx_font_has_char(font, 90) != 1 { return 6 } // 'Z' 61 62 // ===== Step 3: render text "42" to framebuffer ================== 63 let buf: *u8 = (sys_mmap(8)) as *u8 64 buf[0] = 52 as u8 // '4' 65 buf[1] = 50 as u8 // '2' 66 let adv: nx_size = nx_text_render_string(fb, font, 0, 0, buf, 2) 67 if adv != 6 { return 7 } 68 // Some pixels lit (depends on stub glyph patterns) 69 if nx_fb_1bpp_count_on(fb) <= 0 { return 8 } 70 71 // ===== Step 4: q14_math camera setup ============================ 72 // Camera at (3.0, 3.0, 4.0) looking +X (normalized) 73 let cam_x_q14: nx_int = 3 * NX_Q14 74 let cam_y_q14: nx_int = 3 * NX_Q14 75 let cam_z_q14: nx_int = 4 * NX_Q14 76 77 // Initial dir = (1, 0, 0); verify it normalizes to length 1 78 let len: nx_int = nx_q14_vec3_length(NX_Q14, 0, 0) 79 if nx_q14_abs(len - NX_Q14) > 500 { return 9 } 80 81 // Rotate dir by 45° around Z axis using sin/cos 82 let cos45: nx_int = nx_q14_cos(NX_Q14_2PI / 8) 83 let sin45: nx_int = nx_q14_sin(NX_Q14_2PI / 8) 84 // dx' = dx*cos - dy*sin = 1 * cos - 0 * sin = cos45 85 // dy' = dx*sin + dy*cos = 1 * sin + 0 * cos = sin45 86 let dir_x_rotated: nx_int = cos45 87 let dir_y_rotated: nx_int = sin45 88 // length of (cos45, sin45) = 1 89 let rot_len: nx_int = nx_q14_vec3_length(dir_x_rotated, dir_y_rotated, 0) 90 if nx_q14_abs(rot_len - NX_Q14) > 1000 { return 10 } 91 92 // ===== Step 5: raycast in the rotated direction ================ 93 let ray: *NxRay = nx_ray_new(cam_x_q14, cam_y_q14, cam_z_q14, 94 dir_x_rotated, dir_y_rotated, 0, 32) 95 let hit: *NxRayHit = (sys_mmap(56)) as *NxRayHit 96 let v: nx_int = nx_raycast_voxel(ray, world, hit) 97 // Depending on world content the ray will hit something at this angle 98 // through the terrain. Either HIT or MAX_STEPS / OUT_OF_BOX is OK. 99 if v == NX_RV_HIT { 100 // dither the hit voxel as bright 101 nx_fb_1bpp_dither_4(fb, 10, 8, 6, 4, 3) 102 } 103 104 // ===== Step 6: input drives the camera ========================== 105 let input: *NxInputGpio = nx_input_gpio_new(10000, 500000) 106 // Press LEFT at t=15000 (past 10ms debounce on first poll) 107 let edge: nx_int = nx_input_gpio_poll(input, NX_BTN_LEFT, 1, 15000) 108 if edge != NX_BE_PRESSED { return 11 } 109 if nx_input_gpio_is_pressed(input, NX_BTN_LEFT) != 1 { return 12 } 110 111 // ===== Step 7: forensic assertions ============================== 112 // World was generated with cells > 0 113 if cells_filled <= 0 { return 13 } 114 // Framebuffer has SOMETHING rendered 115 if nx_fb_1bpp_count_on(fb) <= 0 { return 14 } 116 // Camera dir normalized properly 117 if nx_q14_abs(rot_len - NX_Q14) > 1000 { return 15 } 118 119 // Stone floor consistency: every (x,y) at z=0 is stone 120 var fx: nx_size = 0 121 while fx < 16 { 122 var fy: nx_size = 0 123 while fy < 16 { 124 if nx_tissue_get(world, fx, fy, 0) != NX_WS_PAL_STONE { return 16 } 125 fy = fy + 1 126 } 127 fx = fx + 1 128 } 129 130 return 0 131}