package nes import ( "crecher.net/gones/v2/cart" "crecher.net/gones/v2/cpu" "crecher.net/gones/v2/ppu" ) type Console struct { CPU *cpu.CPU PPU *ppu.PPU Cart *cart.Cartridge RAM [0x800]uint8 // APU, controllers once they exist cycles uint64 } func New(c *cart.Cartridge) *Console { n := &Console{Cart: c} n.PPU = ppu.New(c.Mapper, c.Mirroring) n.CPU = cpu.NewCPU(n) n.CPU.Reset() return n } // Step runs one CPU instruction and the PPU dots (3 per CPU cycle) that // elapse alongside it, firing NMI on a PPUCTRL/vblank edge. PPU state seen // by bus accesses mid-instruction still lags behind by up to one // instruction's worth of dots, since the CPU isn't cycle-stepped yet (see // README: "CPU stall hook and proper IRQ/NMI line (polled between // instructions)") - timing-sensitive register logic (the $2002 race window, // NMI edge detection) is implemented to the real dot granularity and is // exercised directly in the ppu package's tests, but end-to-end precision // against real games/test ROMs needs that CPU work to land too. func (n *Console) Step() int { cycles := n.CPU.Step() n.cycles += uint64(cycles) for i := 0; i < cycles*3; i++ { n.PPU.Tick() if n.PPU.PollNMI() { n.CPU.NMI() } } return cycles } func (n *Console) StepFrame() { // 29780 is roughly one frame: // ~ 1789773 cycles per sec // ~ 60.1 fps // 1789773 / 60.1 = 29779.916... const cyclesPerFrame = 29780 for done := 0; done < cyclesPerFrame; { done += n.Step() } }