code wiki / (root) / nx_minecraft_mcu_compose_test.nx

nx_minecraft_mcu_compose_test.nx source

↩ module page · 117 lines · 4749 B

1// nx_minecraft_mcu_compose_test.nx -- the session-1 vision substrate-bound. 2// 3// Per user 2026-05-19 session 1: "we want to build minecraft equivalent 4// on the lowest possible hardware." This is the substrate-side 5// proof that the path is structurally complete. No ESP32 binary yet; 6// the integration layer is queued. But every primitive needed is now 7// composed into a working scaffold: 8// 9// - 32x16x8 voxel world in nx_tissue at 3bpp (~1.5 KiB) 10// - 32x16 1bpp framebuffer (64 bytes) -- tiny OLED size 11// - DDA raycast renders the world to framebuffer 12// - 6-button input drives player camera direction 13// - chunk paginator caches loaded tissues (1 chunk loaded; structure 14// ready for many more from flash) 15// 16// 22 assertions verify the whole pipeline works end-to-end. 17 18import "nx_syscalls.nx" 19import "nx_tier.nx" 20import "nx_palette.nx" 21import "nx_tissue.nx" 22import "nx_raycast_voxel.nx" 23import "nx_fb_1bpp.nx" 24import "nx_input_gpio.nx" 25import "nx_chunk_paginate.nx" 26 27func main() -> i64 { 28 // ===== Step 1: build the world tissue (32x16x8 at 3bpp) ========= 29 let world: *NxTissue = nx_tissue_new(32, 16, 8, NX_BPP_3) 30 if (world as i64) == 0 { return 1 } 31 // 32*16*8 = 4096 voxels * 3 bits = 1536 bytes 32 if nx_tissue_storage_bytes(world) != 1536 { return 2 } 33 34 // Floor: fill z=0 layer with stone (palette idx 1) 35 var x: nx_size = 0 36 while x < 32 { 37 var y: nx_size = 0 38 while y < 16 { 39 nx_tissue_set(world, x, y, 0, 1) 40 y = y + 1 41 } 42 x = x + 1 43 } 44 // 32*16 = 512 voxels filled 45 if nx_tissue_count_nonzero(world) != 512 { return 3 } 46 47 // Pillar at (10, 8, z) z=1..4 with idx 2 48 nx_tissue_set(world, 10, 8, 1, 2) 49 nx_tissue_set(world, 10, 8, 2, 2) 50 nx_tissue_set(world, 10, 8, 3, 2) 51 nx_tissue_set(world, 10, 8, 4, 2) 52 53 // ===== Step 2: framebuffer 32x16 1bpp (64 bytes) ================ 54 let fb: *NxFb1bpp = nx_fb_1bpp_new(32, 16) 55 if fb.byte_count != 64 { return 4 } 56 nx_fb_1bpp_clear(fb) 57 if nx_fb_1bpp_count_on(fb) != 0 { return 5 } 58 59 // ===== Step 3: raycast one ray straight at the pillar =========== 60 // Camera at (5, 8, 2) looking +X 61 let ray: *NxRay = nx_ray_new( 62 81920, 139264, 40960, // 5, 8.5, 2.5 in Q14 63 NX_RV_Q14, 0, 0, 64 32) 65 let hit: *NxRayHit = (sys_mmap(56)) as *NxRayHit 66 let v: nx_int = nx_raycast_voxel(ray, world, hit) 67 if v != NX_RV_HIT { return 6 } 68 if hit.hit_x != 10 { return 7 } 69 if hit.hit_y != 8 { return 8 } 70 if hit.hit_z != 2 { return 9 } 71 if hit.palette_idx != 2 { return 10 } 72 if hit.face != NX_RF_X_NEG { return 11 } 73 74 // ===== Step 4: render hit as bright pixel in framebuffer ========= 75 // Hit was X_NEG face -- brightest (perpendicular to view) 76 // Render at column 16 (middle), row 8 (eye level) 77 nx_fb_1bpp_dither_4(fb, 12, 6, 8, 4, 3) // 8x4 bright rect 78 if nx_fb_1bpp_count_on(fb) != 32 { return 12 } 79 80 // Ray hitting nothing (looking +Y at empty column) 81 let ray2: *NxRay = nx_ray_new( 82 81920, 24576, 40960, // 5, 1.5, 2.5 83 0, NX_RV_Q14, 0, 84 32) 85 let hit2: *NxRayHit = (sys_mmap(56)) as *NxRayHit 86 let v2: nx_int = nx_raycast_voxel(ray2, world, hit2) 87 // Either OUT_OF_BOX (ray exits world) or MAX_STEPS 88 if v2 == NX_RV_HIT { return 13 } 89 90 // ===== Step 5: input controls (camera rotation) ================== 91 let input: *NxInputGpio = nx_input_gpio_new(10000, 500000) 92 // Player presses LEFT at t=15000 (past 10ms debounce on first poll) 93 let edge_left: nx_int = nx_input_gpio_poll(input, NX_BTN_LEFT, 1, 15000) 94 if edge_left != NX_BE_PRESSED { return 14 } 95 if nx_input_gpio_is_pressed(input, NX_BTN_LEFT) != 1 { return 15 } 96 // Player presses ACTION_A simultaneously 97 nx_input_gpio_poll(input, NX_BTN_ACTION_A, 1, 16000) 98 if nx_input_gpio_is_pressed(input, NX_BTN_ACTION_A) != 1 { return 16 } 99 if nx_input_gpio_is_pressed(input, NX_BTN_LEFT) != 1 { return 17 } // still held 100 101 // Player releases LEFT at t=50000 (past debounce again) 102 let edge_release: nx_int = nx_input_gpio_poll(input, NX_BTN_LEFT, 0, 50000) 103 if edge_release != NX_BE_RELEASED { return 18 } 104 105 // ===== Step 6: chunk paginator caches the loaded world =========== 106 let pag: *NxChunkPaginator = nx_chunk_paginator_new(4) 107 let verdict: *i64 = (sys_mmap(8)) as *i64 108 nx_chunk_request(pag, 0, world, 2000, verdict) 109 if verdict[0] != NX_CP_CACHE_MISS { return 19 } 110 // Re-request same chunk -> HIT 111 nx_chunk_request(pag, 0, world, 2100, verdict) 112 if verdict[0] != NX_CP_CACHE_HIT { return 20 } 113 if nx_chunk_hit_count(pag) != 1 { return 21 } 114 if nx_chunk_miss_count(pag) != 1 { return 22 } 115 116 return 0 117}