Wild Wood C64 Parallax Engine Deconstructed
This technical breakdown examines the parallax scrolling engine and sprite management systems implemented in The Wild Wood, an independent Commodore 64 title featuring advanced multi-layer graphics.
The Wild Wood’s engine development spanned multiple iterations, evolving from a basic scrolling prototype toward a sophisticated multi-layer system capable of effects rarely attempted in C64 games. Each technical decision represented trade-offs between visual ambition and implementation complexity—the resulting architecture reflects accumulated solutions to challenges encountered throughout development.
This deconstruction documents the final engine state, explaining both the implemented solutions and the reasoning that guided technical choices. Understanding the “why” behind implementation decisions provides more transferable value than bare documentation of the “what.”
Dual-Interrupt Architecture
The engine operates through combined Non-Maskable Interrupt and raster interrupt execution. NMI handlers preempt active IRST processing to execute sprite multiplexing operations while character and color RAM manipulation continues. This dual-threaded model enables fixed-position sprite management without raster stalls or system instability—NMI priority ensures consistent sprite positioning regardless of IRST workload.
The architectural decision to combine interrupt types emerged from competing timing requirements. Parallax scrolling demands precise screen-position-synchronized operations—color changes, bank switches, scroll register updates must occur at specific raster positions. Simultaneously, sprite multiplexing requires cycle-accurate timing independent of screen position—sprite repositioning must complete within specific cycle windows regardless of where on screen those windows fall.
Single-interrupt architectures force compromise. Using only raster interrupts for both scrolling and sprites creates complex nested interrupt structures where timing becomes fragile. Using only NMIs abandons the position-synchronization that raster interrupts provide naturally. The dual architecture assigns each interrupt type to its natural domain: raster IRQs handle position-critical display operations; NMIs handle cycle-critical sprite operations.
The preemption behavior proves critical. When an NMI fires during IRQ execution, the processor suspends IRQ processing, services the NMI, then resumes the IRQ. This guarantees NMI timing regardless of IRQ workload—sprite multiplexing occurs with consistent precision even when scrolling operations run long. The IRQ may complete late, but the NMI never waits.
Optimized Color Scrolling
Color RAM limitations prevent double-buffering available to character data. The implemented solution tracks color block boundaries rather than scrolling complete rows—only leading and trailing edge positions update when continuous color spans exceed 8 characters. This optimization recovers approximately 6-8 raster lines per layer while preserving visual fidelity for terrain backgrounds.
The C64’s color RAM occupies fixed addresses ($D800-$DBFF) that cannot be bank-switched. While character RAM can exist in multiple locations (enabling double-buffering), color RAM has only one instance. Every color change during scrolling must write to this single location, creating a bottleneck impossible to eliminate through memory architecture.
The optimization exploits visual characteristics of natural terrain. Forest backgrounds typically contain extended horizontal regions of similar color—a band of tree canopy, a stretch of undergrowth, a section of forest floor. These regions share color across multiple character columns; only the boundaries between regions require color changes.
The engine tracks boundary positions for each color region within each scrolling layer. During scroll operations, only boundary characters receive color RAM updates. A 40-column screen row with 4 color transitions requires updating 8 color bytes (4 left edges, 4 right edges) rather than 40 bytes for full-row updates.
This boundary-tracking approach requires advance knowledge of level color structure—data prepared during level design rather than computed at runtime. The trade-off of increased level data size proves acceptable given the substantial runtime savings.
Coordinated Layer Updates
Multi-speed parallax layers require careful synchronization to prevent frame budget overruns. Hardware scroll offsets stagger across layers such that character RAM updates rarely coincide. When player movement patterns threaten synchronization, a deferral flag postpones one layer’s update to the subsequent frame, enabling dual-layer character scrolling within single IRST timing windows.
Consider the timing challenge: three parallax layers scrolling at different speeds means three independent software scroll cycles, each requiring character RAM updates when hardware scroll positions wrap. If layers’ wrap points coincide (all three triggering character updates on the same frame), total processing exceeds the available frame budget.
The staggering strategy offsets each layer’s scroll phase relative to others. When one layer begins its 8-pixel hardware scroll cycle, other layers are mid-cycle. This distributes character update load across frames—typically at most one major update per frame, with occasional two-update frames when phase relationships temporarily align.
The deferral mechanism handles unavoidable collisions. When two layers require simultaneous updates and available time permits only one, the lower-priority layer sets a deferral flag. On the next frame, the deferred update executes with priority. Players may perceive a single frame of slightly incorrect scroll positioning, but the visual impact proves minimal compared to alternative solutions (frame rate drops, incomplete updates).
Movement Tables
Each layer references independent speed lookup tables maintaining proportional ratios. Rather than fixed pixel-per-frame values, tables encode cumulative displacement across frame sequences, supporting four acceleration levels plus stationary positions for natural momentum transitions.
Fixed-speed scrolling creates artificial-feeling movement. Real-world motion involves acceleration, deceleration, and momentum—objects don’t instantly achieve maximum speed or stop immediately. The table-based system enables smooth velocity transitions that approximate natural physics within 8-bit integer arithmetic constraints.
Each table entry encodes position offset for the current frame and velocity state for the next frame. Acceleration patterns might span 8-12 frames from stationary to maximum speed, with each frame advancing velocity state and applying the corresponding position offset. Deceleration follows similar patterns, potentially with different timing to create asymmetric acceleration/braking feel.
The proportional ratio maintenance ensures parallax layers maintain consistent relative speeds across velocity transitions. A distant mountain layer moving at 1/4 foreground speed maintains that ratio during acceleration—it doesn’t reach maximum speed while the foreground is still accelerating.
Sprite Stability Management
The player sprite crosses multiple raster split boundaries requiring jitter compensation. Per-frame Y-position sampling enables dynamic cycle-delay adjustment within Double IRQ stabilization routines. This counteracts sprite-induced cycle stealing that would otherwise cause visible IRST trigger point variation.
The VIC-II steals CPU cycles during sprite data fetching—up to 3 cycles per sprite per scanline where sprites are present. This cycle stealing varies based on sprite count and positions, creating variable latency in interrupt handler execution. If an interrupt fires during a sprite-heavy region, handler execution begins later than during sprite-free regions.
For raster effects requiring cycle-exact timing, this variable latency produces visible jitter. Color splits might waver horizontally; scroll positions might shift by one or two pixels; multiplexed sprites might show brief glitches.
The Double IRQ technique achieves stable timing by triggering two interrupts in rapid succession. The first interrupt’s exact timing varies due to sprite stealing; it immediately sets up a second interrupt timed to occur after the variable region. The second interrupt triggers with consistent timing, providing a stable reference point for subsequent operations.
The per-frame Y-position sampling enables dynamic adjustment. If the player sprite moves vertically between frames, the cycle-stealing profile changes. The engine samples current sprite positions at frame start, calculates expected stealing patterns, and adjusts Double IRQ timing accordingly.
Multiplexing Precision
Vertically distributed sprites (environmental objects) demand exact timing to prevent visual artifacts. Strategic NOP instruction placement prevents premature rendering while avoiding ghost-images from off-screen sprites with high-byte position flags set.
Sprite multiplexing repositions sprites after they’ve been rendered, allowing them to appear at multiple vertical positions within the same frame. The timing window for repositioning is narrow—sprite data fetching for the new position must not begin until the old rendering completes.
Repositioning too early produces “ghost” artifacts where partial sprite data from the new position appears during the old position’s render. Repositioning too late means the sprite misses its new render window, either failing to appear or appearing on the subsequent frame with visible flicker.
NOP instruction sequences create precise timing delays. Each NOP consumes exactly 2 cycles; sequences of NOPs provide sub-scanline timing adjustment. The engine calculates required delay based on sprite positions and inserts appropriate NOP counts before each repositioning operation.
The high-byte position flag ($D010) controls sprite horizontal positioning beyond 255 pixels. When sprites are repositioned off-screen (for multiplexing setup), incorrect high-byte handling creates artifacts—sprites appear at X position 0-7 instead of the intended off-screen location. The engine explicitly manages these flags during all repositioning operations.
Dynamic Sprite Loading
VIC bank constraints necessitate runtime sprite definition streaming from main RAM. Transfer operations execute during sprite invisibility windows, using optimized loops within main-loop processing rather than interrupt context to preserve handler timing.
The VIC-II accesses a 16K memory bank for all graphics operations. Sprite data must reside within this bank to be displayable. With screen RAM, character definitions, and multiple sprite frames competing for this 16K, sprite memory fills quickly. Animation sequences with many frames exceed available space.
Streaming addresses this constraint by copying sprite data from main RAM (outside the VIC bank) into VIC-accessible buffers on demand. While a sprite displays one animation frame, the engine copies the next frame into an alternate buffer. When animation advances, the sprite pointer switches to the prepared buffer.
The transfer timing exploits rendering patterns. Each sprite is invisible during portions of the frame—before its Y position and after its render completes. During these windows, the engine can modify sprite data without affecting displayed graphics. The main loop, rather than interrupt handlers, performs transfers to avoid increasing interrupt latency.
Transfer efficiency matters for large sprites or multiple simultaneous animations. Optimized copy loops using self-modifying code or undocumented opcodes reduce transfer cycles. For particularly demanding situations, transfers split across multiple frames, updating portions of large sprites incrementally.
Enhancement Roadmap
Potential optimizations include screen double-buffering for reduced raster overhead, music player integration improvements, and Commodore 128 adaptation exploiting 2MHz border processing and native color RAM double-buffering.
Screen double-buffering would extend the bank-switching approach currently used for parallax layers to additional screen elements. The trade-off involves increased memory consumption against reduced per-frame update overhead—a trade-off that becomes more favorable as scroll complexity increases.
Music player integration improvements would address timing interactions between the audio subsystem and visual effects. Currently, music playback occurs during specific frame windows; expanding these windows or distributing playback across multiple calls would improve audio quality without sacrificing visual stability.
Commodore 128 adaptation offers substantial capability expansion. The C128’s 2MHz mode operates during border regions (when the VIC-IIe is not accessing memory), effectively doubling available processing time for setup operations. Additionally, the VDC chip provides independent color memory that could substitute for the C64’s problematic fixed-address color RAM, enabling true color double-buffering.
See also: vertical parallax scrolling · geo-referenced sprite positioning · bank-switched scrolling · Parallaxian game page