use anyhow::Result; use prometeu_bytecode::assembler::assemble; use prometeu_bytecode::model::{ BytecodeModule, ConstantPoolEntry, DebugInfo, Export, FunctionMeta, SyscallDecl, }; use std::fs; use std::path::PathBuf; fn asm(s: &str) -> Vec { assemble(s).expect("assemble") } pub fn generate() -> Result<()> { let mut rom: Vec = Vec::new(); let syscalls = vec![ SyscallDecl { module: "gfx".into(), name: "clear_565".into(), version: 1, arg_slots: 1, ret_slots: 0, }, SyscallDecl { module: "gfx".into(), name: "draw_disc".into(), version: 1, arg_slots: 5, ret_slots: 0, }, SyscallDecl { module: "gfx".into(), name: "draw_text".into(), version: 1, arg_slots: 4, ret_slots: 0, }, SyscallDecl { module: "log".into(), name: "write".into(), version: 1, arg_slots: 2, ret_slots: 0, }, SyscallDecl { module: "gfx".into(), name: "set_sprite".into(), version: 1, arg_slots: 10, ret_slots: 1, }, ]; heavy_load(&mut rom); // light_load(&mut rom); let functions = vec![FunctionMeta { code_offset: 0, code_len: rom.len() as u32, param_slots: 0, local_slots: 2, return_slots: 0, max_stack_slots: 16, }]; let module = BytecodeModule { version: 0, const_pool: vec![ ConstantPoolEntry::String("stress".into()), ConstantPoolEntry::String("frame".into()), ConstantPoolEntry::String("missing_glyph_bank".into()), ], functions, code: rom, debug_info: Some(DebugInfo { pc_to_span: vec![], function_names: vec![(0, "main".into())], }), exports: vec![Export { symbol: "main".into(), func_idx: 0 }], syscalls, }; let bytes = module.serialize(); let mut out_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); out_dir.pop(); // pbxgen-stress out_dir.pop(); // tools out_dir.pop(); // crates out_dir.push("test-cartridges"); out_dir.push("stress-console"); fs::create_dir_all(&out_dir)?; fs::write(out_dir.join("program.pbx"), bytes)?; let assets_pa_path = out_dir.join("assets.pa"); if assets_pa_path.exists() { fs::remove_file(&assets_pa_path)?; } fs::write(out_dir.join("manifest.json"), b"{\n \"magic\": \"PMTU\",\n \"cartridge_version\": 1,\n \"app_id\": 1,\n \"title\": \"Stress Console\",\n \"app_version\": \"0.1.0\",\n \"app_mode\": \"Game\",\n \"capabilities\": [\"gfx\", \"log\"]\n}\n")?; Ok(()) } #[allow(dead_code)] fn heavy_load(rom: &mut Vec) { // Single function 0: main // Everything runs here — no coroutines, no SPAWN, no YIELD. // // Global 0 = t (frame counter) // Local 0 = scratch // Local 1 = loop counter for discs // // Loop: // t = (t + 1) // clear screen // draw 500 discs using t for animation // draw 20 texts using t for animation // RET (runtime handles the frame loop) // --- init locals --- // local 0: scratch // local 1: loop counter for discs rom.extend(asm("PUSH_I32 0\nSET_LOCAL 0\nPUSH_I32 0\nSET_LOCAL 1")); // --- t = (t + 1) --- // t is global 0 to persist across prepare_call resets rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 1\nADD\nSET_GLOBAL 0")); // --- clear screen --- rom.extend(asm("PUSH_I32 0\nHOSTCALL 0")); // --- call status-first syscall path once per frame and drop status --- rom.extend(asm( "PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 4\nPOP_N 1", )); // --- draw 500 discs --- rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1")); let disc_loop_start = rom.len() as u32; rom.extend(asm("GET_LOCAL 1\nPUSH_I32 500\nLT")); let jif_disc_end_offset = rom.len() + 2; rom.extend(asm("JMP_IF_FALSE 0")); // x = (t * (i+7) + i * 13) % 320 rom.extend(asm("GET_GLOBAL 0\nGET_LOCAL 1\nPUSH_I32 7\nADD\nMUL\nGET_LOCAL 1\nPUSH_I32 13\nMUL\nADD\nPUSH_I32 320\nMOD")); // y = (t * (i+11) + i * 17) % 180 rom.extend(asm("GET_GLOBAL 0\nGET_LOCAL 1\nPUSH_I32 11\nADD\nMUL\nGET_LOCAL 1\nPUSH_I32 17\nMUL\nADD\nPUSH_I32 180\nMOD")); // r = ( (i*13) % 20 ) + 5 rom.extend(asm("GET_LOCAL 1\nPUSH_I32 13\nMUL\nPUSH_I32 20\nMOD\nPUSH_I32 5\nADD")); // border color = (i * 1234) & 0xFFFF rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1234\nMUL\nPUSH_I32 65535\nBIT_AND")); // fill color = (i * 5678 + t) & 0xFFFF rom.extend(asm("GET_LOCAL 1\nPUSH_I32 5678\nMUL\nGET_GLOBAL 0\nADD\nPUSH_I32 65535\nBIT_AND")); // HOSTCALL gfx.draw_disc (x, y, r, border, fill) rom.extend(asm("HOSTCALL 1")); // i++ rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1\nADD\nSET_LOCAL 1")); let jmp_disc_loop_offset = rom.len() + 2; rom.extend(asm("JMP 0")); let disc_loop_end = rom.len() as u32; // --- draw 20 texts --- rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1")); let text_loop_start = rom.len() as u32; rom.extend(asm("GET_LOCAL 1\nPUSH_I32 20\nLT")); let jif_text_end_offset = rom.len() + 2; rom.extend(asm("JMP_IF_FALSE 0")); // x = (t * 3 + i * 40) % 320 rom.extend(asm( "GET_GLOBAL 0\nPUSH_I32 3\nMUL\nGET_LOCAL 1\nPUSH_I32 40\nMUL\nADD\nPUSH_I32 320\nMOD", )); // y = (i * 30 + t) % 180 rom.extend(asm("GET_LOCAL 1\nPUSH_I32 30\nMUL\nGET_GLOBAL 0\nADD\nPUSH_I32 180\nMOD")); // string (toggle between "stress" and "frame") rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1\nBIT_AND\nPUSH_I32 0\nNEQ")); let jif_text_alt_offset = rom.len() + 2; rom.extend(asm("JMP_IF_FALSE 0")); rom.extend(asm("PUSH_CONST 0")); // "stress" let jmp_text_join_offset = rom.len() + 2; rom.extend(asm("JMP 0")); let text_alt_target = rom.len() as u32; rom.extend(asm("PUSH_CONST 1")); // "frame" let text_join_target = rom.len() as u32; // color = (t * 10 + i * 1000) & 0xFFFF rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 10\nMUL\nGET_LOCAL 1\nPUSH_I32 1000\nMUL\nADD\nPUSH_I32 65535\nBIT_AND")); // HOSTCALL gfx.draw_text (x, y, str, color) rom.extend(asm("HOSTCALL 2")); // i++ rom.extend(asm("GET_LOCAL 1\nPUSH_I32 1\nADD\nSET_LOCAL 1")); let jmp_text_loop_offset = rom.len() + 2; rom.extend(asm("JMP 0")); let text_loop_end = rom.len() as u32; // --- log every 60 frames --- rom.extend(asm("GET_GLOBAL 0\nPUSH_I32 60\nMOD\nPUSH_I32 0\nEQ")); let jif_log_offset = rom.len() + 2; rom.extend(asm("JMP_IF_FALSE 0")); rom.extend(asm("PUSH_I32 2\nPUSH_CONST 0\nHOSTCALL 3")); let after_log = rom.len() as u32; // --- end of function --- rom.extend(asm("FRAME_SYNC\nRET")); // --- Patch jump targets --- let patch = |buf: &mut Vec, imm_offset: usize, target: u32| { buf[imm_offset..imm_offset + 4].copy_from_slice(&target.to_le_bytes()); }; patch(rom, jif_disc_end_offset, disc_loop_end); patch(rom, jmp_disc_loop_offset, disc_loop_start); patch(rom, jif_text_end_offset, text_loop_end); patch(rom, jif_text_alt_offset, text_alt_target); patch(rom, jmp_text_join_offset, text_join_target); patch(rom, jmp_text_loop_offset, text_loop_start); patch(rom, jif_log_offset, after_log); }