Super Mario Derivations
This article brilliantly demonstrates the power of Nix's lazy evaluation, an often-misunderstood core feature, by applying it to an unconventional problem. The author cleverly represents Super Mario Bros. 3 button sequences and game states as Nix derivations, effectively turning the Nix store into a game save history. It's a compelling, practical exploration that showcases Nix's capabilities far beyond package management, appealing to those who love innovative uses of technical tools.
The Lowdown
The article dives into the often-surprising lazy evaluation feature of the Nix language and its profound implications, illustrating how this characteristic enables complex structures like those found in Nixpkgs. It then takes this concept to an unexpected and creative extreme: using Nix to simulate Super Mario Bros. 3.
- Nix's laziness means only accessed attributes are evaluated, permitting "endless" recursion in attribute sets without immediate termination. This is exemplified by
pkgs.pkgs.pkgs.helloyielding the same store path, or an infinitely deepcountdownattribute set where only indexed values are computed. - This lazy, recursive nature is applied to Super Mario Bros. 3, where each attribute path represents a sequence of button presses. Each node in the tree is a game frame, and children are button presses leading to new frames.
- The system generates a Nix derivation for each frame/button press, with each derivation taking the previous press's savestate as an input. This ensures that frames are not re-emulated, and the Nix store effectively becomes a history of game savestates.
- This design allows for efficient branching: building a new sequence that shares a prefix with an existing one only computes the divergent presses, reusing all common ancestor derivations from the store.
- A
.playsuffix in the attribute path stitches together all generated frames into a video, leveraging the pre-computed frames already present in the Nix store via symlinks for optimal performance. - The author identifies practical limits: Nix's
max-call-depthcaps path length (around 20,000 presses) due to nested calls, and the kernel'sMAX_ARG_STRLENlimits argument size (around 21,845 presses). These can be overcome by adjustingmax-call-depthor providing the input sequence via a file. - Build times for these derivations are linear per press. Surprisingly, the round-trip cost for checking cache substituters is a more significant bottleneck than the actual frame emulation.
This innovative project demonstrates that Nix's attribute paths are not merely names but rather "programs" that can lazily generate content. It highlights Nix's potential as a robust persistence layer for reproducible state machines, extending its utility well beyond conventional package management to fascinating applications like game emulation.