## 1. Project Overview & Quickstart (gbdev/resources.html)

# Technical Documentation: gbdev/resources.html

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/gbdev/resources.html)

# resources.html

### Agent Skills & Rules
- Discovered 21 agent rule(s) and prompt configurations.



--- METRICS ---
- Files Extracted: 1
- Estimated Token Budget: ~26 tokens
- Canonical Reference: https://codewiki.google/github.com/gbdev/resources.html


## 2. Official Technical Reference & Guides (gbdev/gbdev.github.io)

## File: README.md

# gbdev website

This is the source repository of main website of the Game Boy development iniative, hosted at [gbdev.io](https://gbdev.io). Powered by Vuepress 2.x

You need Node (> 14.x) and yarn.

```bash
# Install dependencies
yarn

# Generate the resources page
yarn build-resources

# Start a local hot-reloading development server at http://localhost:8080
yarn dev

# Build website (also generates the resources page)
yarn build
```

See [CONTRIBUTING.md](CONTRIBUTING.md) to learn how to contribute to the website, including how the resources page is generated.

---

## File: CONTRIBUTING.md

# Contributing

## Resources page

`website/resources.md` (served at [gbdev.io/resources](https://gbdev.io/resources)) is not committed: it's generated from the [awesome-gbdev](https://github.com/gbdev/awesome-gbdev) README by `yarn build-resources`. The website-only top of the page (front matter, videos, notes) lives in [`scripts/resources/header.md`](scripts/resources/header.md), and everything between the `` and `` markers of the awesome-gbdev README is appended to it.

By default the README is fetched from the master branch of awesome-gbdev. Run this before `yarn dev`, and again whenever you want to pull in changes to the list (requires Node 18 or later):

```bash
# From the master branch of awesome-gbdev
yarn build-resources

# From a specific branch, tag or commit
yarn build-resources 0c457e7833cd22985976dc0eecc859919ae589b0

# From a local copy of the README (e.g. to preview changes to the list md)
yarn build-resources ../awesome-gbdev/README.md
```

`yarn build` runs `yarn build-resources` automatically before building. By default (when `RESOURCES_MD_SRC` is not set) the script fetches the README from the master branch of awesome-gbdev on GitHub by itself, so deployed builds always use the current list. To build from another source instead, set the `RESOURCES_MD_SRC` environment variable to any of the values accepted by `yarn build-resources` (a branch, tag, commit or local path):

```bash
# Fetches the README from awesome-gbdev master
yarn build

# Uses a local copy of the README instead
RESOURCES_MD_SRC=../awesome-gbdev/README.md yarn build
```

## CI

The website is rebuilt and deployed on every push to `dev`, and also whenever the awesome-gbdev README changes: awesome-gbdev sends an `awesome-gbdev-updated` [`repository_dispatch`](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#repository_dispatch) event to this repository, which runs the deploy workflow. To do so, awesome-gbdev holds a fine-grained token with access to this repository (Contents: read and write): if it expires or is revoked, the resources page stops updating until the next push here. How the event is sent is documented in the [awesome-gbdev CONTRIBUTING.md](https://github.com/gbdev/awesome-gbdev/blob/master/CONTRIBUTING.md). A deploy may also be started manually from the Actions tab ("Run workflow").

---

## File: package.json

{
  "name": "gbdev.github.io",
  "version": "1.0.0",
  "description": "Source repository of the gbdev landing website. GitHub Pages serves the content of the `master` branch on [gbdev.github.io](https://gbdev.github.io).",
  "main": "index.js",
  "scripts": {
    "dev": "vuepress dev website",
    "prebuild": "node scripts/build-resources.mjs",
    "build": "vuepress build website",
    "build-resources": "node scripts/build-resources.mjs"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/gbdev/gbdev.github.io.git"
  },
  "bugs": {
    "url": "https://github.com/gbdev/gbdev.github.io/issues"
  },
  "homepage": "https://gbdev.io",
  "devDependencies": {
    "@vuepress/bundler-vite": "^2.0.0-rc.31",
    "@vuepress/plugin-shiki": "^2.0.0-rc.136",
    "@vuepress/theme-default": "^2.0.0-rc.136",
    "sass-embedded": "^1.86.1",
    "vue-inline-svg": "^4.0.0",
    "vuepress": "^2.0.0-rc.31"
  },
  "dependencies": {}
}

---

## File: scripts/resources/header.md

---
title: Resources
lang: en-US
head:
  - - meta
    - name: og:title
      content: Game Boy dev resources
  - - meta
    - name: og:description
      content: A curated list of awesome Development resources, tools, docs, related projects and homebrews.
---


Awesome
    Game Boy Development



A curated list of awesome Game Boy (Color) Development resources, tools, docs, related projects and homebrews. Inspired by the [ awesome ](https://github.com/sindresorhus/awesome) list thing.



This project is open source and community-lead. Come [contribute! ](https://gbdev.io/contributing)




[Chat with the community!](https://gbdev.io/chat)



## Introduction

### [The Game Boy, a hardware autopsy](https://www.youtube.com/playlist?list=PLu3xpmdUP-GRDp8tknpXC_Y4RUQtMMqEu)







### [The Ultimate Game Boy Talk](https://media.ccc.de/v/33c3-8029-the_ultimate_game_boy_talk)







::: note Disambiguation: GBA vs GB/GBC

###### Game Boy Advance

Game Boy Advance development is covered by another project, the [awesome-GBAdev](https://github.com/gbdev/awesome-gbadev) list.
GBA, however, can run GB/GBC games. It does so in a slightly different way compared to native hardware. This is covered in the Emulator Development section of this list.

###### Game Boy Color and Super Game Boy

This list is focused on the original (1989) Game Boy (DMG). _Game Boy Color_ (GBC) and _Super Game Boy_ (SGB) are very similar systems, with a few important distinctions, such as:

- Different hardware specifications
- Specific hardware and software features
- Specific registers
- Specific bugs, quirks and exploitable behaviours

If you aim to develop your software for SGB or GBC, or you want to know how it runs on the other systems, you may want to take advantage and adapt to these differences, check the [Game Boy Color](#game-boy-color) category and look for specific references to GBC/CGB and SGB.

:::

---

## File: website/README.md

---
sidebar: false
head:
  - - meta
    - name: og:title
      content: gbdev.io homepage
  - - meta
    - name: og:description
      content: >
        Home of the Game Boy and Game Boy Advance homebrew development scene.
        We are working on (open) development tools, documentation and digital
        preservation.
editLink: false
---




                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
                        
    



    Game Boy Development
community


        
We are a non for profit collective of passionate developers and hackers working on development tools, homebrew games, emulators, preservation and documentation for the Nintendo Game Boy handheld console, the original gray brick from 1989.  Join us on <a target="_blank" href="https://github.com/gbdev">GitHub</a>, <a target="_blank" rel="me" href="https://fedi.gbdev.io/@gbdev">Mastodon</a>, <a target="_blank" href="https://bsky.app/profile/gbdev.io">BlueSky</a>, <a target="_blank" href="https://twitter.com/gbdev0">Twitter</a> and [Discord](chat.html).





# Projects

Here's what we are up to:

 <a class="projectTitle" target="_blank" href="https://gbdev.io/pandocs/">Pan Docs&nbsp;</a><a class="github-button" target="_blank" href="https://github.com/gbdev/pandocs" data-icon="octicon-star" data-show-count="true" aria-label="Star gbdev/awesome-gbdev on GitHub">Star</a>

The single, most comprehensive technical reference to Game Boy available to the public. 


#### [Game Boy CPU Opcode tables](https://gbdev.io/gb-opcodes/optables)


<a class="projectTitle" href="resources.html">awesome-gbdev&nbsp;</a> <a class="github-button" target="_blank" href="https://github.com/gbdev/awesome-gbdev" data-icon="octicon-star" data-show-count="true" aria-label="Star gbdev/awesome-gbdev on GitHub">Star</a>

Curated list of Game Boy development resources such as tools, guides, technical documentation, tutorials, emulators, related projects and open-source ROMs. Everything you'll ever need to know and see about this console is here. If you want to code an emulator, create your own game or simply dive into the software and hardware architecture of the Game Boy, this is the place!

<a class="projectTitle" target="_blank" href="https://rgbds.gbdev.io/">RGBDS&nbsp;</a> <a class="github-button" target="_blank" href="https://github.com/gbdev/rgbds" data-icon="octicon-star" data-show-count="true" aria-label="Star gbdev/rgbds on GitHub">Star</a>

Rednex Game Boy Development System: the de-facto ASM development toolkit for the Game Boy and Game Boy Color.


[hardware.inc](https://github.com/gbdev/hardware.inc) - Standard include file containing Game Boy hardware definitions for use in RGBDS projects.

[rgbds-live](https://gbdev.io/rgbds-live/) - A live Game Boy programming environment in the browser, allowing for realtime assembly programming with RGBDS.



<a class="projectTitle" target="_blank" href="https://gbdev.io/GBEmulatorShootout/">GB Emulator Shootout&nbsp;</a> <a class="github-button" target="_blank" href="https://github.com/gbdev/GBEmulatorShootout" data-icon="octicon-star" data-show-count="true" aria-label="Star gbdev/GBEmulatorShootout on GitHub">Star</a>

An automated test comparison project for Game Boy emulators. It runs a suite of accuracy test ROMs across a set of Game Boy emulators and publishes the pass/fail results as a comprehensive table, with screenshots for each test case.

<a class="projectTitle" target="_blank" href="https://gbdev.io/gb-asm-tutorial/">GB ASM Tutorial&nbsp;</a> <a class="github-button" target="_blank" href="https://github.com/gbdev/gb-asm-tutorial" data-icon="octicon-star" data-show-count="true" aria-label="Star gbdev/gb-asm-tutorial on GitHub">Star</a>

A (work in progress) tutorial on how to program for the Game Boy in assembly, touching on every aspect required to make Game Boy games, via a Hello World, constructing an Arkanoid clone, and capping off by making a playable Shoot-'Em-Up.
<a class="projectTitle" target="_blank" href="https://github.com/gbdk-2020/gbdk-2020">GBDK 2020&nbsp;</a> <a class="github-button" target="_blank" href="https://github.com/gbdk-2020/gbdk-2020" data-icon="octicon-star" data-show-count="true" aria-label="Star gbdev/rgbds on GitHub">Star</a>

Maintained and modernized GBDK, the Game Boy Development Kit. Now powered by an updated version of the SDCC toolchain, provides a C compiler, assembler, linker and a set of libraries.

[Chat Channels](https://gbdev.io/chat)&nbsp;
The places where our community thrives. Here we **chat**, discuss, help each other and show what we are working on. We have a Discord server, an IRC channel, and more.

 <a target="_blank" href="https://hh.gbdev.io">Homebrew Hub</a>&nbsp;
Play Game Boy games online from an archive of hundreds of entries! 
A community-led attempt to collect, archive and save every unofficial game, homebrew, demo, patch, hackrom for Game Boy produced by the community through the last 3 decades of passionate work.

<a target="_blank" href="https://github.com/gb-archive">The Game Boy Archive</a>&nbsp;
Digital library of Game Boy related software, hardware and literature. Aimed to mirror and preserve old and fragmented contributions in the scene from the last three decades.

<a target="_blank" href="https://cameraclub.gbdev.io">Game Boy Camera Club</a>
Released in 1998 as a Nintendo accessory for the original handheld console, the Game Boy Camera became an instant hit with kids.
Despite technological advances, its capturing sub-megapixel charm endures, capturing the imagination of photographers who embrace its unique, low-fi aesthetic.

Events
We host coding competitions in which anyone can partecipate by creating original games, demos, homebrews tools and music for the Game Boy and compete for glory and prizes.

<a target="_blank" href="https://itch.io/jam/gbcompo21"></a>&nbsp;&nbsp;<a target="_blank" href="https://itch.io/jam/gbcompo23"></a>&nbsp;&nbsp;<a target="_blank" href="https://itch.io/jam/gbcompo25"></a>




<smalL>

For Game Boy _Advance_ development, check [gbadev.net](https://gbadev.net).

</small>

<script>
export default {
  mounted () {
    let githubButtonsScript = document.createElement('script')
    githubButtonsScript.setAttribute('src', 'https://buttons.github.io/buttons.js')
    document.head.appendChild(githubButtonsScript)

    let colors = ['#5f98c6', // Teal (1998, 1999 NA) 
        '#FCCF37', // Dandelion - yellow (1998, 1999 NA)
        '#EB1667', // Berry - fuchsia (1998, 1999 NA)
        '#A8F602', // Kiwi - neon green (1998, 1999 NA)
        // '#5B3099', // Grape - purple (1998) TODO: Darken the black elements
        // Translucent colors - TODO: Fix speakers
        'rgba(255, 255, 255, 0.5)', // Atomic Purple - clear purple (1998)
        'rgba(91, 48, 153, 0.5)' // Neotones Ice (LIJI32 special request <3)
    ]
    let color = colors[Math.floor(Math.random() * colors.length)];
    var box = document.querySelector('#box');
    box.style.setProperty('background-color', color);
    console.log(color)
  }
}
</script>

<style>
body{
        font-size:18px !important;
}

.projectTitle{
        font-size:32px;
        letter-spacing: -0.05em;
        line-height: 80%;
}

/*
By `heero`. Originally found at https://codepen.io/heero/pen/wylhv
*/

#outer {
    width: 100%;
}
#container {
  position: relative;
  width: 130px;
  height: 220px;
  margin: 15px 20px 50px 50px;
}
@media (min-width: 560px) {
  #container {
  float: right;
  }
}

#back {
  position: absolute;
  bottom: 0;
  background-color: #D3D3D3;
  box-shadow: 5px 5px 0 0 #D3D3D3;
  -webkit-animation: backAnim 5s forwards;
  -moz-animation: backAnim 5s forwards;
  -o-animation: backAnim 5s forwards;
  animation: backAnim 5s forwards;
}

@-webkit-keyframes backAnim {
  0% {
    width: 8px;
    height: 68px;
  }
  4% {
    width: 100px;
    height: 75px;
  }
  5% {
    width: 125px;
    height: 80px;
    border-radius: 3px 3px 20px 3px;
  }
  11%, 100% {
    height: 206px;
    width: 125px;
    border-radius: 3px 3px 20px 3px;
  }
}
@-moz-keyframes backAnim {
  0% {
    width: 8px;
    height: 68px;
  }
  4% {
    width: 100px;
    height: 75px;
  }
  5% {
    width: 125px;
    height: 80px;
    border-radius: 3px 3px 20px 3px;
  }
  11%, 100% {
    height: 206px;
    width: 125px;
    border-radius: 3px 3px 20px 3px;
  }
}
@-ms-keyframes backAnim {
  0% {
    width: 8px;
    height: 68px;
  }
  4% {
    width: 100px;
    height: 75px;
  }
  5% {
    width: 125px;
    height: 80px;
    border-radius: 3px 3px 20px 3px;
  }
  11%, 100% {
    height: 206px;
    width: 125px;
    border-radius: 3px 3px 20px 3px;
  }
}
@keyframes backAnim {
  0% {
    width: 8px;
    height: 68px;
  }
  4% {
    width: 100px;
    height: 75px;
  }
  5% {
    width: 125px;
    height: 80px;
    border-radius: 3px 3px 20px 3px;
  }
  11%, 100% {
    height: 206px;
    width: 125px;
    border-radius: 3px 3px 20px 3px;
  }
}
#speaker {
  position: absolute;
  background-color: #646060;
  -webkit-animation: speakerAnim 5s forwards;
  -moz-animation: speakerAnim 5s forwards;
  -o-animation: speakerAnim 5s forwards;
  animation: speakerAnim 5s forwards;
}

@-webkit-keyframes speakerAnim {
  0%, 6% {
    width: 0px;
    height: 0px;
    bottom: 22px;
    left: 103px;
  }
  11% {
    width: 30px;
    height: 30px;
    bottom: 7px;
    left: 88px;
    border-radius: 15px;
    box-shadow: inset 0px 0px 0px 15px #484848;
  }
  14%, 100% {
    width: 30px;
    height: 30px;
    bottom: 7px;
    left: 88px;
    border-radius: 15px;
    box-shadow: inset 0px 0px 0px 8px #484848;
  }
}
@-moz-keyframes speakerAnim {
  0%, 6% {
    width: 0px;
    height: 0px;
    bottom: 22px;
    left: 103px;
  }
  11% {
    width: 30px;
    height: 30px;
    bottom: 7px;
    left: 88px;
    border-radius: 15px;
    box-shadow: inset 0px 0px 0px 15px #484848;
  }
  14%, 100% {
    width: 30px;
    height: 30px;
    bottom: 7px;
    left: 88px;
    border-radius: 15px;
    box-shadow: inset 0px 0px 0px 8px #484848;
  }
}
@-ms-keyframes speakerAnim {
  0%, 6% {
    width: 0px;
    height: 0px;
    bottom: 22px;
    left: 103px;
  }
  11% {
    width: 30px;
    height: 30px;
    bottom: 7px;
    left: 88px;
    border-radius: 15px;
    box-shadow: inset 0px 0px 0px 15px #484848;
  }
  14%, 100% {
    width: 30px;
    height: 30px;
    bottom: 7px;
    left: 88px;
    border-radius: 15px;
    box-shadow: inset 0px 0px 0px 8px #484848;
  }
}
@keyframes speakerAnim {
  0%, 6% {
    width: 0px;
    height: 0px;
    bottom: 22px;
    left: 103px;
  }
  11% {
    width: 30px;
    height: 30px;
    bottom: 7px;
    left: 88px;
    border-radius: 15px;
    box-shadow: inset 0px 0px 0px 15px #484848;
  }
  14%, 100% {
    width: 30px;
    height: 30px;
    bottom: 7px;
    left: 88px;
    border-radius: 15px;
    box-shadow: inset 0px 0px 0px 8px #484848;
  }
}
#card1 {
  position: absolute;
  left: 15px;
  background-color: #5eaf89;
  -webkit-animation: card1Anim 5s forwards;
  -moz-animation: card1Anim 5s forwards;
  -o-animation: card1Anim 5s forwards;
  animation: card1Anim 5s forwards;
}

@-webkit-keyframes card1Anim {
  0%, 8% {
    height: 0px;
    width: 0px;
    bottom: 64px;
  }
  9% {
    height: 17px;
    width: 15px;
    bottom: 64px;
  }
  12% {
    height: 113px;
    width: 15px;
    bottom: 17px;
  }
  18%, 100% {
    height: 113px;
    width: 95px;
    bottom: 17px;
  }
}
@-moz-keyframes card1Anim {
  0%, 8% {
    height: 0px;
    width: 0px;
    bottom: 64px;
  }
  9% {
    height: 17px;
    width: 15px;
    bottom: 64px;
  }
  12% {
    height: 113px;
    width: 15px;
    bottom: 17px;
  }
  18%, 100% {
    height: 113px;
    width: 95px;
    bottom: 17px;
  }
}
@-ms-keyframes card1Anim {
  0%, 8% {
    height: 0px;
    width: 0px;
    bottom: 64px;
  }
  9% {
    height: 17px;
    width: 15px;
    bottom: 64px;
  }
  12% {
    height: 113px;
    width: 15px;
    bottom: 17px;
  }
  18%, 100% {
    height: 113px;
    width: 95px;
    bottom: 17px;
  }
}
@keyframes card1Anim {
  0%, 8% {
    height: 0px;
    width: 0px;
    bottom: 64px;
  }
  9% {
    height: 17px;
    width: 15px;
    bottom: 64px;
  }
  12% {
    height: 113px;
    width: 15px;
    bottom: 17px;
  }
  18%, 100% {
    height: 113px;
    width: 95px;
    bottom: 17px;
  }
}
#border {
  position: absolute;
  bottom: 2px;
  right: 7px;
  border: 2px solid #646060;
  border-radius: 3px 3px 20px 3px;
  -webkit-animation: borderAnim 5s forwards;
  -moz-animation: borderAnim 5s forwards;
  -o-animation: borderAnim 5s forwards;
  animation: borderAnim 5s forwards;
}

@-webkit-keyframes borderAnim {
  0%, 8% {
    height: 0px;
    width: 0px;
    border: none;
  }
  9% {
    height: 40px;
    width: 10px;
    border-right: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
    border-left: none;
  }
  13% {
    height: 40px;
    width: 117px;
    border-right: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
    border-left: none;
  }
  17% {
    height: 198px;
    width: 117px;
    border-right: 2px solid #646060;
    border-left: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
  }
  18%, 100% {
    height: 198px;
    width: 117px;
    border: 2px solid #646060;
  }
}
@-moz-keyframes borderAnim {
  0%, 8% {
    height: 0px;
    width: 0px;
    border: none;
  }
  9% {
    height: 40px;
    width: 10px;
    border-right: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
    border-left: none;
  }
  13% {
    height: 40px;
    width: 117px;
    border-right: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
    border-left: none;
  }
  17% {
    height: 198px;
    width: 117px;
    border-right: 2px solid #646060;
    border-left: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
  }
  18%, 100% {
    height: 198px;
    width: 117px;
    border: 2px solid #646060;
  }
}
@-ms-keyframes borderAnim {
  0%, 8% {
    height: 0px;
    width: 0px;
    border: none;
  }
  9% {
    height: 40px;
    width: 10px;
    border-right: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
    border-left: none;
  }
  13% {
    height: 40px;
    width: 117px;
    border-right: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
    border-left: none;
  }
  17% {
    height: 198px;
    width: 117px;
    border-right: 2px solid #646060;
    border-left: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
  }
  18%, 100% {
    height: 198px;
    width: 117px;
    border: 2px solid #646060;
  }
}
@keyframes borderAnim {
  0%, 8% {
    height: 0px;
    width: 0px;
    border: none;
  }
  9% {
    height: 40px;
    width: 10px;
    border-right: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
    border-left: none;
  }
  13% {
    height: 40px;
    width: 117px;
    border-right: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
    border-left: none;
  }
  17% {
    height: 198px;
    width: 117px;
    border-right: 2px solid #646060;
    border-left: 2px solid #646060;
    border-bottom: 2px solid #646060;
    border-top: none;
  }
  18%, 100% {
    height: 198px;
    width: 117px;
    border: 2px solid #646060;
  }
}
#card2 {
  position: absolute;
  bottom: 6px;
  left: 7px;
  width: 10px;
  height: 20px;
  background-color: #646060;
  -webkit-animation: card2Anim 5s forwards;
  -moz-animation: card2Anim 5s forwards;
  -o-animation: card2Anim 5s forwards;
  animation: card2Anim 5s forwards;
}

@-webkit-keyframes card2Anim {
  0%, 12% {
    height: 0px;
    width: 0px;
  }
  13% {
    height: 20px;
    width: 10px;
  }
  21%, 100% {
    height: 20px;
    width: 78px;
  }
}
@-moz-keyframes card2Anim {
  0%, 12% {
    height: 0px;
    width: 0px;
  }
  13% {
    height: 20px;
    width: 10px;
  }
  21%, 100% {
    height: 20px;
    width: 78px;
  }
}
@-ms-keyframes card2Anim {
  0%, 12% {
    height: 0px;
    width: 0px;
  }
  13% {
    height: 20px;
    width: 10px;
  }
  21%, 100% {
    height: 20px;
    width: 78px;
  }
}
@keyframes card2Anim {
  0%, 12% {
    height: 0px;
    width: 0px;
  }
  13% {
    height: 20px;
    width: 10px;
  }
  21%, 100% {
    height: 20px;
    width: 78px;
  }
}
#card3 {
  position: absolute;
  bottom: 48px;
  left: 8px;
  height: 5px;
  background-color: #3c9b66;
  -webkit-animation: card3Anim 5s forwards;
  -moz-animation: card3Anim 5s forwards;
  -o-animation: card3Anim 5s forwards;
  animation: card3Anim 5s forwards;
}

@-webkit-keyframes card3Anim {
  0%, 12% {
    height: 0px;
    width: 0px;
  }
  15% {
    height: 5px;
    width: 110px;
  }
  23%, 100% {
    height: 150px;
    width: 110px;
  }
}
@-moz-keyframes card3Anim {
  0%, 12% {
    height: 0px;
    width: 0px;
  }
  15% {
    height: 5px;
    width: 110px;
  }
  23%, 100% {
    height: 150px;
    width: 110px;
  }
}
@-ms-keyframes card3Anim {
  0%, 12% {
    height: 0px;
    width: 0px;
  }
  15% {
    height: 5px;
    width: 110px;
  }
  23%, 100% {
    height: 150px;
    width: 110px;
  }
}
@keyframes card3Anim {
  0%, 12% {
    height: 0px;
    width: 0px;
  }
  15% {
    height: 5px;
    width: 110px;
  }
  23%, 100% {
    height: 150px;
    width: 110px;
  }
}
#chip {
  position: absolute;
  bottom: 46px;
  left: 54px;
  width: 4px;
  height: 4px;
  background-color: #484848;
  -webkit-animation: chipAnim 5s forwards;
  -moz-animation: chipAnim 5s forwards;
  -o-animation: chipAnim 5s forwards;
  animation: chipAnim 5s forwards;
}

@-webkit-keyframes chipAnim {
  0%, 15% {
    height: 0px;
    width: 0px;
  }
  16% {
    height: 4px;
    width: 4px;
    bottom: 46px;
    left: 54px;
  }
  20%, 100% {
    height: 22px;
    width: 22px;
    bottom: 37px;
    left: 45px;
  }
}
@-moz-keyframes chipAnim {
  0%, 15% {
    height: 0px;
    width: 0px;
  }
  16% {
    height: 4px;
    width: 4px;
    bottom: 46px;
    left: 54px;
  }
  20%, 100% {
    height: 22px;
    width: 22px;
    bottom: 37px;
    left: 45px;
  }
}
@-ms-keyframes chipAnim {
  0%, 15% {
    height: 0px;
    width: 0px;
  }
  16% {
    height: 4px;
    width: 4px;
    bottom: 46px;
    left: 54px;
  }
  20%, 100% {
    height: 22px;
    width: 22px;
    bottom: 37px;
    left: 45px;
  }
}
@keyframes chipAnim {
  0%, 15% {
    height: 0px;
    width: 0px;
  }
  16% {
    height: 4px;
    width: 4px;
    bottom: 46px;
    left: 54px;
  }
  20%, 100% {
    height: 22px;
    width: 22px;
    bottom: 37px;
    left: 45px;
  }
}
#chip2 {
  position: absolute;
  bottom: 132px;
  left: 60px;
  width: 4px;
  height: 12px;
  background-color: #484848;
  -webkit-animation: chip2Anim 5s forwards;
  -moz-animation: chip2Anim 5s forwards;
  -o-animation: chip2Anim 5s forwards;
  animation: chip2Anim 5s forwards;
}

@-webkit-keyframes chip2Anim {
  0%, 16% {
    height: 0px;
    width: 0px;
  }
  17% {
    height: 12px;
    width: 4px;
    bottom: 132px;
    left: 60px;
  }
  23% {
    height: 12px;
    width: 60px;
    bottom: 132px;
    left: 32px;
  }
  30%, 100% {
    height: 12px;
    width: 60px;
    bottom: 160px;
    left: 32px;
  }
}
@-moz-keyframes chip2Anim {
  0%, 16% {
    height: 0px;
    width: 0px;
  }
  17% {
    height: 12px;
    width: 4px;
    bottom: 132px;
    left: 60px;
  }
  23% {
    height: 12px;
    width: 60px;
    bottom: 132px;
    left: 32px;
  }
  30%, 100% {
    height: 12px;
    width: 60px;
    bottom: 160px;
    left: 32px;
  }
}
@-ms-keyframes chip2Anim {
  0%, 16% {
    height: 0px;
    width: 0px;
  }
  17% {
    height: 12px;
    width: 4px;
    bottom: 132px;
    left: 60px;
  }
  23% {
    height: 12px;
    width: 60px;
    bottom: 132px;
    left: 32px;
  }
  30%, 100% {
    height: 12px;
    width: 60px;
    bottom: 160px;
    left: 32px;
  }
}
@keyframes chip2Anim {
  0%, 16% {
    height: 0px;
    width: 0px;
  }
  17% {
    height: 12px;
    width: 4px;
    bottom: 132px;
    left: 60px;
  }
  23% {
    height: 12px;
    width: 60px;
    bottom: 132px;
    left: 32px;
  }
  30%, 100% {
    height: 12px;
    width: 60px;
    bottom: 160px;
    left: 32px;
  }
}
#volumeWheelBack {
  position: absolute;
  left: 108px;
  bottom: 157px;
  width: 10px;
  height: 10px;
  background-color: #645d5f;
  -webkit-animation: volumeWheelBackAnim 5s forwards;
  -moz-animation: volumeWheelBackAnim 5s forwards;
  -o-animation: volumeWheelBackAnim 5s forwards;
  animation: volumeWheelBackAnim 5s forwards;
}

@-webkit-keyframes volumeWheelBackAnim {
  0%, 17% {
    height: 0px;
    width: 0px;
    left: 113px;
    bottom: 162px;
  }
  21%, 25% {
    height: 10px;
    width: 10px;
    left: 108px;
    bottom: 157px;
  }
  30%, 100% {
    height: 10px;
    width: 10px;
    left: 108px;
    bottom: 167px;
  }
}
@-moz-keyframes volumeWheelBackAnim {
  0%, 17% {
    height: 0px;
    width: 0px;
    left: 113px;
    bottom: 162px;
  }
  21%, 25% {
    height: 10px;
    width: 10px;
    left: 108px;
    bottom: 157px;
  }
  30%, 100% {
    height: 10px;
    width: 10px;
    left: 108px;
    bottom: 167px;
  }
}
@-ms-keyframes volumeWheelBackAnim {
  0%, 17% {
    height: 0px;
    width: 0px;
    left: 113px;
    bottom: 162px;
  }
  21%, 25% {
    height: 10px;
    width: 10px;
    left: 108px;
    bottom: 157px;
  }
  30%, 100% {
    height: 10px;
    width: 10px;
    left: 108px;
    bottom: 167px;
  }
}
@keyframes volumeWheelBackAnim {
  0%, 17% {
    height: 0px;
    width: 0px;
    left: 113px;
    bottom: 162px;
  }
  21%, 25% {
    height: 10px;
    width: 10px;
    left: 108px;
    bottom: 157px;
  }
  30%, 100% {
    height: 10px;
    width: 10px;
    left: 108px;
    bottom: 167px;
  }
}
#volumeWheel {
  position: absolute;
  left: 108px;
  bottom: 157px;
  width: 16px;
  height: 16px;
  border-radius: 8px;
  background-color: #b2aea9;
  box-shadow: inset 0px 0px 0px 5px #dddddd;
  -webkit-animation: volumeWheelAnim 5s forwards;
  -moz-animation: volumeWheelAnim 5s forwards;
  -o-animation: volumeWheelAnim 5s forwards;
  animation: volumeWheelAnim 5s forwards;
}

@-webkit-keyframes volumeWheelAnim {
  0%, 18% {
    height: 1px;
    width: 1px;
    left: 115px;
    bottom: 159px;
    box-shadow: inset 0px 0px 0px 10px #dddddd;
  }
  22% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 154px;
    box-shadow: inset 0px 0px 0px 10px #dddddd;
  }
  25% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 154px;
    box-shadow: inset 0px 0px 0px 5px #dddddd;
  }
  30%, 100% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 164px;
    box-shadow: inset 0px 0px 0px 5px #dddddd;
  }
}
@-moz-keyframes volumeWheelAnim {
  0%, 18% {
    height: 1px;
    width: 1px;
    left: 115px;
    bottom: 159px;
    box-shadow: inset 0px 0px 0px 10px #dddddd;
  }
  22% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 154px;
    box-shadow: inset 0px 0px 0px 10px #dddddd;
  }
  25% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 154px;
    box-shadow: inset 0px 0px 0px 5px #dddddd;
  }
  30%, 100% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 164px;
    box-shadow: inset 0px 0px 0px 5px #dddddd;
  }
}
@-ms-keyframes volumeWheelAnim {
  0%, 18% {
    height: 1px;
    width: 1px;
    left: 115px;
    bottom: 159px;
    box-shadow: inset 0px 0px 0px 10px #dddddd;
  }
  22% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 154px;
    box-shadow: inset 0px 0px 0px 10px #dddddd;
  }
  25% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 154px;
    box-shadow: inset 0px 0px 0px 5px #dddddd;
  }
  30%, 100% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 164px;
    box-shadow: inset 0px 0px 0px 5px #dddddd;
  }
}
@keyframes volumeWheelAnim {
  0%, 18% {
    height: 1px;
    width: 1px;
    left: 115px;
    bottom: 159px;
    box-shadow: inset 0px 0px 0px 10px #dddddd;
  }
  22% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 154px;
    box-shadow: inset 0px 0px 0px 10px #dddddd;
  }
  25% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 154px;
    box-shadow: inset 0px 0px 0px 5px #dddddd;
  }
  30%, 100% {
    height: 16px;
    width: 16px;
    left: 110px;
    bottom: 164px;
    box-shadow: inset 0px 0px 0px 5px #dddddd;
  }
}
#chipPinL {
  position: absolute;
  left: 43px;
  bottom: 54px;
  width: 4px;
  height: 2px;
  background-color: #dddddd;
  box-shadow: 0px 3px #dddddd;
  -webkit-animation: chipPinAnimL 5s forwards;
  -moz-animation: chipPinAnimL 5s forwards;
  -o-animation: chipPinAnimL 5s forwards;
  animation: chipPinAnimL 5s forwards;
}

@-webkit-keyframes chipPinAnimL {
  0%, 19% {
    width: 0px;
  }
  20% {
    width: 4px;
    box-shadow: 0px 3px #dddddd;
  }
  21% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd;
  }
  22%, 100% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd, 0px 12px #dddddd, 0px 15px #dddddd;
  }
}
@-moz-keyframes chipPinAnimL {
  0%, 19% {
    width: 0px;
  }
  20% {
    width: 4px;
    box-shadow: 0px 3px #dddddd;
  }
  21% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd;
  }
  22%, 100% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd, 0px 12px #dddddd, 0px 15px #dddddd;
  }
}
@-ms-keyframes chipPinAnimL {
  0%, 19% {
    width: 0px;
  }
  20% {
    width: 4px;
    box-shadow: 0px 3px #dddddd;
  }
  21% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd;
  }
  22%, 100% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd, 0px 12px #dddddd, 0px 15px #dddddd;
  }
}
@keyframes chipPinAnimL {
  0%, 19% {
    width: 0px;
  }
  20% {
    width: 4px;
    box-shadow: 0px 3px #dddddd;
  }
  21% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd;
  }
  22%, 100% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd, 0px 12px #dddddd, 0px 15px #dddddd;
  }
}
#chipPinR {
  position: absolute;
  left: 65px;
  bottom: 54px;
  width: 4px;
  height: 2px;
  background-color: #dddddd;
  box-shadow: 0px 3px #dddddd;
  -webkit-animation: chipPinAnimR 5s forwards;
  -moz-animation: chipPinAnimR 5s forwards;
  -o-animation: chipPinAnimR 5s forwards;
  animation: chipPinAnimR 5s forwards;
}

@-webkit-keyframes chipPinAnimR {
  0%, 22% {
    width: 0px;
  }
  23% {
    width: 4px;
    box-shadow: 0px 3px #dddddd;
  }
  24% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd;
  }
  25%, 100% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd, 0px 12px #dddddd, 0px 15px #dddddd;
  }
}
@-moz-keyframes chipPinAnimR {
  0%, 22% {
    width: 0px;
  }
  23% {
    width: 4px;
    box-shadow: 0px 3px #dddddd;
  }
  24% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd;
  }
  25%, 100% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd, 0px 12px #dddddd, 0px 15px #dddddd;
  }
}
@-ms-keyframes chipPinAnimR {
  0%, 22% {
    width: 0px;
  }
  23% {
    width: 4px;
    box-shadow: 0px 3px #dddddd;
  }
  24% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd;
  }
  25%, 100% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd, 0px 12px #dddddd, 0px 15px #dddddd;
  }
}
@keyframes chipPinAnimR {
  0%, 22% {
    width: 0px;
  }
  23% {
    width: 4px;
    box-shadow: 0px 3px #dddddd;
  }
  24% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd;
  }
  25%, 100% {
    box-shadow: 0px 3px #dddddd, 0px 6px #dddddd, 0px 9px #dddddd, 0px 12px #dddddd, 0px 15px #dddddd;
  }
}
#ec1 {
  position: absolute;
  left: 24px;
  bottom: 122px;
  width: 4px;
  height: 4px;
  border-radius: 2px;
  background-color: #484848;
  -webkit-animation: ec3Anim 5s forwards;
  -moz-animation: ec3Anim 5s forwards;
  -o-animation: ec3Anim 5s forwards;
  animation: ec3Anim 5s forwards;
}

@-webkit-keyframes ec3Anim {
  0%, 18% {
    width: 0px;
  }
  20% {
    width: 4px;
    height: 4px;
  }
  22% {
    box-shadow: 8px 0px #484848;
  }
  24% {
    box-shadow: 8px 0px #484848, 16px 0px #484848;
  }
  26% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848;
  }
  28% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848, 32px 0px #484848;
  }
  30%, 100% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848, 32px 0px #484848, 40px 0px #484848;
  }
}
@-moz-keyframes ec3Anim {
  0%, 18% {
    width: 0px;
  }
  20% {
    width: 4px;
    height: 4px;
  }
  22% {
    box-shadow: 8px 0px #484848;
  }
  24% {
    box-shadow: 8px 0px #484848, 16px 0px #484848;
  }
  26% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848;
  }
  28% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848, 32px 0px #484848;
  }
  30%, 100% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848, 32px 0px #484848, 40px 0px #484848;
  }
}
@-ms-keyframes ec3Anim {
  0%, 18% {
    width: 0px;
  }
  20% {
    width: 4px;
    height: 4px;
  }
  22% {
    box-shadow: 8px 0px #484848;
  }
  24% {
    box-shadow: 8px 0px #484848, 16px 0px #484848;
  }
  26% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848;
  }
  28% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848, 32px 0px #484848;
  }
  30%, 100% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848, 32px 0px #484848, 40px 0px #484848;
  }
}
@keyframes ec3Anim {
  0%, 18% {
    width: 0px;
  }
  20% {
    width: 4px;
    height: 4px;
  }
  22% {
    box-shadow: 8px 0px #484848;
  }
  24% {
    box-shadow: 8px 0px #484848, 16px 0px #484848;
  }
  26% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848;
  }
  28% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848, 32px 0px #484848;
  }
  30%, 100% {
    box-shadow: 8px 0px #484848, 16px 0px #484848, 24px 0px #484848, 32px 0px #484848, 40px 0px #484848;
  }
}
#joystickBack {
  position: absolute;
  left: 8px;
  bottom: 47px;
  width: 36px;
  height: 36px;
  border-radius: 18px;
  background-color: #dddddd;
  -webkit-animation: joystickBackAnim 5s forwards;
  -moz-animation: joystickBackAnim 5s forwards;
  -o-animation: joystickBackAnim 5s forwards;
  animation: joystickBackAnim 5s forwards;
}

@-webkit-keyframes joystickBackAnim {
  0%, 23% {
    left: 26px;
    bottom: 65px;
    width: 0px;
    height: 0px;
  }
  28%, 100% {
    left: 8px;
    bottom: 47px;
    width: 36px;
    height: 36px;
  }
}
@-moz-keyframes joystickBackAnim {
  0%, 23% {
    left: 26px;
    bottom: 65px;
    width: 0px;
    height: 0px;
  }
  28%, 100% {
    left: 8px;
    bottom: 47px;
    width: 36px;
    height: 36px;
  }
}
@-ms-keyframes joystickBackAnim {
  0%, 23% {
    left: 26px;
    bottom: 65px;
    width: 0px;
    height: 0px;
  }
  28%, 100% {
    left: 8px;
    bottom: 47px;
    width: 36px;
    height: 36px;
  }
}
@keyframes joystickBackAnim {
  0%, 23% {
    left: 26px;
    bottom: 65px;
    width: 0px;
    height: 0px;
  }
  28%, 100% {
    left: 8px;
    bottom: 47px;
    width: 36px;
    height: 36px;
  }
}
#joystickCross1 {
  position: absolute;
  left: 11px;
  bottom: 60px;
  width: 30px;
  height: 10px;
  border-radius: 1px;
  background-color: #484848;
  -webkit-animation: joystickCrossAnim 5s forwards;
  -moz-animation: joystickCrossAnim 5s forwards;
  -o-animation: joystickCrossAnim 5s forwards;
  animation: joystickCrossAnim 5s forwards;
}

#joystickCross2 {
  position: absolute;
  left: 11px;
  bottom: 60px;
  width: 30px;
  height: 10px;
  border-radius: 1px;
  background-color: #484848;
  -moz-transform: rotate(90deg);
  -ms-transform: rotate(90deg);
  -webkit-transform: rotate(90deg);
  transform: rotate(90deg);
  -webkit-animation: joystickCrossAnim 5s forwards;
  -moz-animation: joystickCrossAnim 5s forwards;
  -o-animation: joystickCrossAnim 5s forwards;
  animation: joystickCrossAnim 5s forwards;
}

@-webkit-keyframes joystickCrossAnim {
  0%, 26% {
    left: 26px;
    bottom: 65px;
    width: 0px;
    height: 0px;
  }
  33%, 100% {
    left: 11px;
    bottom: 60px;
    width: 30px;
    height: 10px;
  }
}
@-moz-keyframes joystickCrossAnim {
  0%, 26% {
    left: 26px;
    bottom: 65px;
    width: 0px;
    height: 0px;
  }
  33%, 100% {
    left: 11px;
    bottom: 60px;
    width: 30px;
    height: 10px;
  }
}
@-ms-keyframes joystickCrossAnim {
  0%, 26% {
    left: 26px;
    bottom: 65px;
    width: 0px;
    height: 0px;
  }
  33%, 100% {
    left: 11px;
    bottom: 60px;
    width: 30px;
    height: 10px;
  }
}
@keyframes joystickCrossAnim {
  0%, 26% {
    left: 26px;
    bottom: 65px;
    width: 0px;
    height: 0px;
  }
  33%, 100% {
    left: 11px;
    bottom: 60px;
    width: 30px;
    height: 10px;
  }
}
#buttonsABBack {
  position: absolute;
  left: 70px;
  bottom: 55px;
  width: 50px;
  height: 22px;
  border-radius: 11px;
  background-color: #dddddd;
  box-shadow: inset 0px 0px 0px 2px #a9a9a9;
  -moz-transform: rotate(160deg);
  -ms-transform: rotate(160deg);
  -webkit-transform: rotate(160deg);
  transform: rotate(160deg);
  -webkit-animation: buttonsABBackAnim 5s forwards;
  -moz-animation: buttonsABBackAnim 5s forwards;
  -o-animation: buttonsABBackAnim 5s forwards;
  animation: buttonsABBackAnim 5s forwards;
}

@-webkit-keyframes buttonsABBackAnim {
  0%, 24% {
    left: 95px;
    bottom: 67px;
    width: 0px;
    height: 0px;
    -moz-transform: rotate(220deg);
    -ms-transform: rotate(220deg);
    -webkit-transform: rotate(220deg);
    transform: rotate(220deg);
  }
  29% {
    left: 70px;
    bottom: 55px;
    width: 50px;
    height: 22px;
    -webkit-transform: rotate(160deg);
    -moz-transform: rotate(160deg);
    -ms-transform: rotate(160deg);
    -webkit-transform: rotate(160deg);
    transform: rotate(160deg);
  }
  33%, 100% {
    box-shadow: inset 0px 0px 0px 2px #a9a9a9;
  }
}
@-moz-keyframes buttonsABBackAnim {
  0%, 24% {
    left: 95px;
    bottom: 67px;
    width: 0px;
    height: 0px;
    -moz-transform: rotate(220deg);
    -ms-transform: rotate(220deg);
    -webkit-transform: rotate(220deg);
    transform: rotate(220deg);
  }
  29% {
    left: 70px;
    bottom: 55px;
    width: 50px;
    height: 22px;
    -webkit-transform: rotate(160deg);
    -moz-transform: rotate(160deg);
    -ms-transform: rotate(160deg);
    -webkit-transform: rotate(160deg);
    transform: rotate(160deg);
  }
  33%, 100% {
    box-shadow: inset 0px 0px 0px 2px #a9a9a9;
  }
}
@-ms-keyframes buttonsABBackAnim {
  0%, 24% {
    left: 95px;
    bottom: 67px;
    width: 0px;
    height: 0px;
    -moz-transform: rotate(220deg);
    -ms-transform: rotate(220deg);
    -webkit-transform: rotate(220deg);
    transform: rotate(220deg);
  }
  29% {
    left: 70px;
    bottom: 55px;
    width: 50px;
    height: 22px;
    -webkit-transform: rotate(160deg);
    -moz-transform: rotate(160deg);
    -ms-transform: rotate(160deg);
    -webkit-transform: rotate(160deg);
    transform: rotate(160deg);
  }
  33%, 100% {
    box-shadow: inset 0px 0px 0px 2px #a9a9a9;
  }
}
@keyframes buttonsABBackAnim {
  0%, 24% {
    left: 95px;
    bottom: 67px;
    width: 0px;
    height: 0px;
    -moz-transform: rotate(220deg);
    -ms-transform: rotate(220deg);
    -webkit-transform: rotate(220deg);
    transform: rotate(220deg);
  }
  29% {
    left: 70px;
    bottom: 55px;
    width: 50px;
    height: 22px;
    -webkit-transform: rotate(160deg);
    -moz-transform: rotate(160deg);
    -ms-transform: rotate(160deg);
    -webkit-transform: rotate(160deg);
    transform: rotate(160deg);
  }
  33%, 100% {
    box-shadow: inset 0px 0px 0px 2px #a9a9a9;
  }
}
#buttonsAB {
  position: absolute;
  left: 74px;
  bottom: 53px;
  width: 16px;
  height: 16px;
  border-radius: 11px;
  background-color: #484848;
  box-shadow: 26px -10px #484848;
  -webkit-animation: buttonsABAnim 5s forwards;
  -moz-animation: buttonsABAnim 5s forwards;
  -o-animation: buttonsABAnim 5s forwards;
  animation: buttonsABAnim 5s forwards;
}

@-webkit-keyframes buttonsABAnim {
  0%, 28% {
    left: 81px;
    bottom: 61px;
    width: 0px;
    height: 0px;
    box-shadow: 26px -10px #484848;
  }
  33%, 100% {
    left: 74px;
    bottom: 53px;
    width: 16px;
    height: 16px;
    box-shadow: 26px -10px #484848;
  }
}
@-moz-keyframes buttonsABAnim {
  0%, 28% {
    left: 81px;
    bottom: 61px;
    width: 0px;
    height: 0px;
    box-shadow: 26px -10px #484848;
  }
  33%, 100% {
    left: 74px;
    bottom: 53px;
    width: 16px;
    height: 16px;
    box-shadow: 26px -10px #484848;
  }
}
@-ms-keyframes buttonsABAnim {
  0%, 28% {
    left: 81px;
    bottom: 61px;
    width: 0px;
    height: 0px;
    box-shadow: 26px -10px #484848;
  }
  33%, 100% {
    left: 74px;
    bottom: 53px;
    width: 16px;
    height: 16px;
    box-shadow: 26px -10px #484848;
  }
}
@keyframes buttonsABAnim {
  0%, 28% {
    left: 81px;
    bottom: 61px;
    width: 0px;
    height: 0px;
    box-shadow: 26px -10px #484848;
  }
  33%, 100% {
    left: 74px;
    bottom: 53px;
    width: 16px;
    height: 16px;
    box-shadow: 26px -10px #484848;
  }
}
#buttonsABText {
  -webkit-animation: buttonsABTextAnim 5s forwards;
  -moz-animation: buttonsABTextAnim 5s forwards;
  -o-animation: buttonsABTextAnim 5s forwards;
  animation: buttonsABTextAnim 5s forwards;
}

#buttonsABText::before {
  content: "A";
  position: absolute;
  left: 106px;
  bottom: 50px;
  font: 8px "Arial";
  color: #606060;
}

#buttonsABText::after {
  content: "B";
  position: absolute;
  left: 80px;
  bottom: 40px;
  font: 8px "Arial";
  color: #606060;
}

@-webkit-keyframes buttonsABTextAnim {
  0%, 73% {
    visibility: hidden;
  }
  74%, 100% {
    visibility: visible;
  }
}
@-moz-keyframes buttonsABTextAnim {
  0%, 73% {
    visibility: hidden;
  }
  74%, 100% {
    visibility: visible;
  }
}
@-ms-keyframes buttonsABTextAnim {
  0%, 73% {
    visibility: hidden;
  }
  74%, 100% {
    visibility: visible;
  }
}
@keyframes buttonsABTextAnim {
  0%, 73% {
    visibility: hidden;
  }
  74%, 100% {
    visibility: visible;
  }
}
#buttonsSSText {
  -webkit-animation: buttonsSSTextAnim 5s forwards;
  -moz-animation: buttonsSSTextAnim 5s forwards;
  -o-animation: buttonsSSTextAnim 5s forwards;
  animation: buttonsSSTextAnim 5s forwards;
}

#buttonsSSText::before {
  content: "select";
  position: absolute;
  left: 45px;
  bottom: 15px;
  font: 7px "Arial";
  color: #606060;
}

#buttonsSSText::after {
  content: "start";
  position: absolute;
  left: 68px;
  bottom: 15px;
  font: 7px "Arial";
  color: #606060;
}

@-webkit-keyframes buttonsSSTextAnim {
  0%, 75% {
    visibility: hidden;
  }
  76%, 100% {
    visibility: visible;
  }
}
@-moz-keyframes buttonsSSTextAnim {
  0%, 75% {
    visibility: hidden;
  }
  76%, 100% {
    visibility: visible;
  }
}
@-ms-keyframes buttonsSSTextAnim {
  0%, 75% {
    visibility: hidden;
  }
  76%, 100% {
    visibility: visible;
  }
}
@keyframes buttonsSSTextAnim {
  0%, 75% {
    visibility: hidden;
  }
  76%, 100% {
    visibility: visible;
  }
}
#buttonsSSBack {
  position: absolute;
  left: 45px;
  bottom: 26px;
  width: 40px;
  height: 10px;
  background-color: #dddddd;
  -webkit-animation: buttonsSSBackAnim 5s forwards;
  -moz-animation: buttonsSSBackAnim 5s forwards;
  -o-animation: buttonsSSBackAnim 5s forwards;
  animation: buttonsSSBackAnim 5s forwards;
}

@-webkit-keyframes buttonsSSBackAnim {
  0%, 27% {
    left: 65px;
    bottom: 26px;
    width: 0px;
    height: 10px;
  }
  33%, 100% {
    left: 45px;
    bottom: 26px;
    width: 40px;
    height: 10px;
  }
}
@-moz-keyframes buttonsSSBackAnim {
  0%, 27% {
    left: 65px;
    bottom: 26px;
    width: 0px;
    height: 10px;
  }
  33%, 100% {
    left: 45px;
    bottom: 26px;
    width: 40px;
    height: 10px;
  }
}
@-ms-keyframes buttonsSSBackAnim {
  0%, 27% {
    left: 65px;
    bottom: 26px;
    width: 0px;
    height: 10px;
  }
  33%, 100% {
    left: 45px;
    bottom: 26px;
    width: 40px;
    height: 10px;
  }
}
@keyframes buttonsSSBackAnim {
  0%, 27% {
    left: 65px;
    bottom: 26px;
    width: 0px;
    height: 10px;
  }
  33%, 100% {
    left: 45px;
    bottom: 26px;
    width: 40px;
    height: 10px;
  }
}
#buttonsSS {
  position: absolute;
  border-radius: 2px;
  background-color: #484848;
  -webkit-animation: buttonsSSAnim 5s forwards;
  -moz-animation: buttonsSSAnim 5s forwards;
  -o-animation: buttonsSSAnim 5s forwards;
  animation: buttonsSSAnim 5s forwards;
}

@-webkit-keyframes buttonsSSAnim {
  0%, 34% {
    left: 52px;
    bottom: 31px;
    width: 0px;
    height: 0px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 26px 0px #a9a8a6;
  }
  41% {
    left: 45px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 26px 0px #a9a8a6;
  }
  44% {
    left: 48px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 20px 0px #a9a8a6;
  }
  49%, 100% {
    left: 48px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 0px #a9a8a6, 20px 0px 0px 0px #484848, 20px 0px #a9a8a6;
  }
}
@-moz-keyframes buttonsSSAnim {
  0%, 34% {
    left: 52px;
    bottom: 31px;
    width: 0px;
    height: 0px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 26px 0px #a9a8a6;
  }
  41% {
    left: 45px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 26px 0px #a9a8a6;
  }
  44% {
    left: 48px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 20px 0px #a9a8a6;
  }
  49%, 100% {
    left: 48px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 0px #a9a8a6, 20px 0px 0px 0px #484848, 20px 0px #a9a8a6;
  }
}
@-ms-keyframes buttonsSSAnim {
  0%, 34% {
    left: 52px;
    bottom: 31px;
    width: 0px;
    height: 0px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 26px 0px #a9a8a6;
  }
  41% {
    left: 45px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 26px 0px #a9a8a6;
  }
  44% {
    left: 48px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 20px 0px #a9a8a6;
  }
  49%, 100% {
    left: 48px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 0px #a9a8a6, 20px 0px 0px 0px #484848, 20px 0px #a9a8a6;
  }
}
@keyframes buttonsSSAnim {
  0%, 34% {
    left: 52px;
    bottom: 31px;
    width: 0px;
    height: 0px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 26px 0px #a9a8a6;
  }
  41% {
    left: 45px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 26px 0px #a9a8a6;
  }
  44% {
    left: 48px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 2px #a9a8a6, 20px 0px 0px -2px #484848, 20px 0px #a9a8a6;
  }
  49%, 100% {
    left: 48px;
    bottom: 29px;
    width: 14px;
    height: 4px;
    box-shadow: inset 0px 0px 0px 0px #a9a8a6, 20px 0px 0px 0px #484848, 20px 0px #a9a8a6;
  }
}
#screenBack {
  position: absolute;
  background-color: #dddddd;
  border-radius: 2px;
  -webkit-animation: screenBackAnim 5s forwards;
  -moz-animation: screenBackAnim 5s forwards;
  -o-animation: screenBackAnim 5s forwards;
  animation: screenBackAnim 5s forwards;
}

@-webkit-keyframes screenBackAnim {
  0%, 25% {
    left: 63px;
    bottom: 125px;
    width: 0px;
    height: 64px;
  }
  31%, 100% {
    left: 21px;
    bottom: 125px;
    width: 84px;
    height: 64px;
  }
}
@-moz-keyframes screenBackAnim {
  0%, 25% {
    left: 63px;
    bottom: 125px;
    width: 0px;
    height: 64px;
  }
  31%, 100% {
    left: 21px;
    bottom: 125px;
    width: 84px;
    height: 64px;
  }
}
@-ms-keyframes screenBackAnim {
  0%, 25% {
    left: 63px;
    bottom: 125px;
    width: 0px;
    height: 64px;
  }
  31%, 100% {
    left: 21px;
    bottom: 125px;
    width: 84px;
    height: 64px;
  }
}
@keyframes screenBackAnim {
  0%, 25% {
    left: 63px;
    bottom: 125px;
    width: 0px;
    height: 64px;
  }
  31%, 100% {
    left: 21px;
    bottom: 125px;
    width: 84px;
    height: 64px;
  }
}
#screen {
  position: absolute;
  background-color: #494949;
  border-radius: 4px 4px 10px 4px;
  -webkit-animation: screenAnim 5s forwards;
  -moz-animation: screenAnim 5s forwards;
  -o-animation: screenAnim 5s forwards;
  animation: screenAnim 5s forwards;
}

@-webkit-keyframes screenAnim {
  0%, 28% {
    left: 68px;
    bottom: 131px;
    width: 0px;
    height: 0px;
  }
  33%, 36% {
    left: 26px;
    bottom: 100px;
    width: 74px;
    height: 64px;
  }
  40%, 42% {
    left: 26px;
    bottom: 114px;
    width: 74px;
    height: 64px;
  }
  48%, 100% {
    left: 8px;
    bottom: 100px;
    width: 110px;
    height: 96px;
  }
}
@-moz-keyframes screenAnim {
  0%, 28% {
    left: 68px;
    bottom: 131px;
    width: 0px;
    height: 0px;
  }
  33%, 36% {
    left: 26px;
    bottom: 100px;
    width: 74px;
    height: 64px;
  }
  40%, 42% {
    left: 26px;
    bottom: 114px;
    width: 74px;
    height: 64px;
  }
  48%, 100% {
    left: 8px;
    bottom: 100px;
    width: 110px;
    height: 96px;
  }
}
@-ms-keyframes screenAnim {
  0%, 28% {
    left: 68px;
    bottom: 131px;
    width: 0px;
    height: 0px;
  }
  33%, 36% {
    left: 26px;
    bottom: 100px;
    width: 74px;
    height: 64px;
  }
  40%, 42% {
    left: 26px;
    bottom: 114px;
    width: 74px;
    height: 64px;
  }
  48%, 100% {
    left: 8px;
    bottom: 100px;
    width: 110px;
    height: 96px;
  }
}
@keyframes screenAnim {
  0%, 28% {
    left: 68px;
    bottom: 131px;
    width: 0px;
    height: 0px;
  }
  33%, 36% {
    left: 26px;
    bottom: 100px;
    width: 74px;
    height: 64px;
  }
  40%, 42% {
    left: 26px;
    bottom: 114px;
    width: 74px;
    height: 64px;
  }
  48%, 100% {
    left: 8px;
    bottom: 100px;
    width: 110px;
    height: 96px;
  }
}
#jackBack {
  position: absolute;
  background-color: #646060;
  -webkit-animation: jackBackAnim 5s forwards;
  -moz-animation: jackBackAnim 5s forwards;
  -o-animation: jackBackAnim 5s forwards;
  animation: jackBackAnim 5s forwards;
}

@-webkit-keyframes jackBackAnim {
  0%, 21% {
    left: 11px;
    bottom: 136px;
    width: 0px;
    height: 0px;
  }
  25%, 31% {
    left: 6px;
    bottom: 132px;
    width: 12px;
    height: 12px;
  }
  36%, 100% {
    left: 6px;
    bottom: 138px;
    width: 12px;
    height: 12px;
  }
}
@-moz-keyframes jackBackAnim {
  0%, 21% {
    left: 11px;
    bottom: 136px;
    width: 0px;
    height: 0px;
  }
  25%, 31% {
    left: 6px;
    bottom: 132px;
    width: 12px;
    height: 12px;
  }
  36%, 100% {
    left: 6px;
    bottom: 138px;
    width: 12px;
    height: 12px;
  }
}
@-ms-keyframes jackBackAnim {
  0%, 21% {
    left: 11px;
    bottom: 136px;
    width: 0px;
    height: 0px;
  }
  25%, 31% {
    left: 6px;
    bottom: 132px;
    width: 12px;
    height: 12px;
  }
  36%, 100% {
    left: 6px;
    bottom: 138px;
    width: 12px;
    height: 12px;
  }
}
@keyframes jackBackAnim {
  0%, 21% {
    left: 11px;
    bottom: 136px;
    width: 0px;
    height: 0px;
  }
  25%, 31% {
    left: 6px;
    bottom: 132px;
    width: 12px;
    height: 12px;
  }
  36%, 100% {
    left: 6px;
    bottom: 138px;
    width: 12px;
    height: 12px;
  }
}
#jack {
  position: absolute;
  background-color: #494949;
  border-radius: 7px;
  -webkit-animation: jackAnim 5s forwards;
  -moz-animation: jackAnim 5s forwards;
  -o-animation: jackAnim 5s forwards;
  animation: jackAnim 5s forwards;
}

@-webkit-keyframes jackAnim {
  0%, 23% {
    left: 6px;
    bottom: 135px;
    width: 0px;
    height: 0px;
  }
  27%, 31% {
    left: 1px;
    bottom: 131px;
    width: 14px;
    height: 14px;
  }
  36%, 100% {
    left: 1px;
    bottom: 137px;
    width: 14px;
    height: 14px;
  }
}
@-moz-keyframes jackAnim {
  0%, 23% {
    left: 6px;
    bottom: 135px;
    width: 0px;
    height: 0px;
  }
  27%, 31% {
    left: 1px;
    bottom: 131px;
    width: 14px;
    height: 14px;
  }
  36%, 100% {
    left: 1px;
    bottom: 137px;
    width: 14px;
    height: 14px;
  }
}
@-ms-keyframes jackAnim {
  0%, 23% {
    left: 6px;
    bottom: 135px;
    width: 0px;
    height: 0px;
  }
  27%, 31% {
    left: 1px;
    bottom: 131px;
    width: 14px;
    height: 14px;
  }
  36%, 100% {
    left: 1px;
    bottom: 137px;
    width: 14px;
    height: 14px;
  }
}
@keyframes jackAnim {
  0%, 23% {
    left: 6px;
    bottom: 135px;
    width: 0px;
    height: 0px;
  }
  27%, 31% {
    left: 1px;
    bottom: 131px;
    width: 14px;
    height: 14px;
  }
  36%, 100% {
    left: 1px;
    bottom: 137px;
    width: 14px;
    height: 14px;
  }
}
#screen2 {
  position: absolute;
  background-color: #b7b28f;
  border-radius: 2px;
  -webkit-animation: screen2Anim 5s forwards;
  -moz-animation: screen2Anim 5s forwards;
  -o-animation: screen2Anim 5s forwards;
  animation: screen2Anim 5s forwards;
}

@-webkit-keyframes screen2Anim {
  0%, 44% {
    left: 59px;
    bottom: 151px;
    width: 0px;
    height: 0px;
  }
  48% {
    left: 54px;
    bottom: 146px;
    width: 10px;
    height: 10px;
  }
  52% {
    left: 22px;
    bottom: 146px;
    width: 80px;
    height: 10px;
  }
  60%, 100% {
    left: 22px;
    bottom: 112px;
    width: 80px;
    height: 74px;
  }
}
@-moz-keyframes screen2Anim {
  0%, 44% {
    left: 59px;
    bottom: 151px;
    width: 0px;
    height: 0px;
  }
  48% {
    left: 54px;
    bottom: 146px;
    width: 10px;
    height: 10px;
  }
  52% {
    left: 22px;
    bottom: 146px;
    width: 80px;
    height: 10px;
  }
  60%, 100% {
    left: 22px;
    bottom: 112px;
    width: 80px;
    height: 74px;
  }
}
@-ms-keyframes screen2Anim {
  0%, 44% {
    left: 59px;
    bottom: 151px;
    width: 0px;
    height: 0px;
  }
  48% {
    left: 54px;
    bottom: 146px;
    width: 10px;
    height: 10px;
  }
  52% {
    left: 22px;
    bottom: 146px;
    width: 80px;
    height: 10px;
  }
  60%, 100% {
    left: 22px;
    bottom: 112px;
    width: 80px;
    height: 74px;
  }
}
@keyframes screen2Anim {
  0%, 44% {
    left: 59px;
    bottom: 151px;
    width: 0px;
    height: 0px;
  }
  48% {
    left: 54px;
    bottom: 146px;
    width: 10px;
    height: 10px;
  }
  52% {
    left: 22px;
    bottom: 146px;
    width: 80px;
    height: 10px;
  }
  60%, 100% {
    left: 22px;
    bottom: 112px;
    width: 80px;
    height: 74px;
  }
}
#box {
  position: absolute;
  background-color: #5f98c6;
  border-radius: 3px 3px 20px 3px;
  height: 206px;
  width: 125px;
  bottom: 0px;
  -webkit-animation: boxAnim 5s forwards;
  -moz-animation: boxAnim 5s forwards;
  -o-animation: boxAnim 5s forwards;
  animation: boxAnim 5s forwards;
}

@-webkit-keyframes boxAnim {
  0%, 67% {
    height: 0px;
    width: 125px;
    bottom: 206px;
  }
  80%, 100% {
    height: 206px;
    width: 125px;
    bottom: 0px;
  }
}
@-moz-keyframes boxAnim {
  0%, 67% {
    height: 0px;
    width: 125px;
    bottom: 206px;
  }
  80%, 100% {
    height: 206px;
    width: 125px;
    bottom: 0px;
  }
}
@-ms-keyframes boxAnim {
  0%, 67% {
    height: 0px;
    width: 125px;
    bottom: 206px;
  }
  80%, 100% {
    height: 206px;
    width: 125px;
    bottom: 0px;
  }
}
@keyframes boxAnim {
  0%, 67% {
    height: 0px;
    width: 125px;
    bottom: 206px;
  }
  80%, 100% {
    height: 206px;
    width: 125px;
    bottom: 0px;
  }
}
#gameboyText {
  position: absolute;
  left: 23px;
  bottom: 103px;
  height: 6px;
  width: 50px;
  color: #b7b28f;
  clip: rect(0px, 0px, 0px, 0px);
  -webkit-animation: gameboyTextAnim 5s forwards;
  -moz-animation: gameboyTextAnim 5s forwards;
  -o-animation: gameboyTextAnim 5s forwards;
  animation: gameboyTextAnim 5s forwards;
}

@-webkit-keyframes gameboyTextAnim {
  0%, 55% {
    clip: rect(0px, 0px, 7px, 0px);
  }
  60%, 100% {
    clip: rect(0px, 50px, 6px, 0px);
  }
}
@-moz-keyframes gameboyTextAnim {
  0%, 55% {
    clip: rect(0px, 0px, 7px, 0px);
  }
  60%, 100% {
    clip: rect(0px, 50px, 6px, 0px);
  }
}
@-ms-keyframes gameboyTextAnim {
  0%, 55% {
    clip: rect(0px, 0px, 7px, 0px);
  }
  60%, 100% {
    clip: rect(0px, 50px, 6px, 0px);
  }
}
@keyframes gameboyTextAnim {
  0%, 55% {
    clip: rect(0px, 0px, 7px, 0px);
  }
  60%, 100% {
    clip: rect(0px, 50px, 6px, 0px);
  }
}
#powerBack {
  position: absolute;
  background-color: #494949;
  width: 15px;
  height: 9px;
  left: 6px;
  bottom: 197px;
  -webkit-animation: powerBackAnim 5s forwards;
  -moz-animation: powerBackAnim 5s forwards;
  -o-animation: powerBackAnim 5s forwards;
  animation: powerBackAnim 5s forwards;
}

@-webkit-keyframes powerBackAnim {
  0%, 23% {
    height: 0px;
  }
  30%, 100% {
    height: 9px;
  }
}
@-moz-keyframes powerBackAnim {
  0%, 23% {
    height: 0px;
  }
  30%, 100% {
    height: 9px;
  }
}
@-ms-keyframes powerBackAnim {
  0%, 23% {
    height: 0px;
  }
  30%, 100% {
    height: 9px;
  }
}
@keyframes powerBackAnim {
  0%, 23% {
    height: 0px;
  }
  30%, 100% {
    height: 9px;
  }
}
#power {
  position: absolute;
  background-color: #494949;
  border-radius: 4px 4px 0px 0px / 2px 2px 0px 0px;
  width: 8px;
  height: 6px;
  visibility: visible;
  -webkit-animation: powerAnim 5s forwards;
  -moz-animation: powerAnim 5s forwards;
  -o-animation: powerAnim 5s forwards;
  animation: powerAnim 5s forwards;
}

@-webkit-keyframes powerAnim {
  0%, 42% {
    visibility: hidden;
    left: 9px;
    bottom: 197px;
  }
  43% {
    visibility: visible;
    left: 9px;
    bottom: 197px;
  }
  46%, 90% {
    visibility: visible;
    left: 9px;
    bottom: 202px;
  }
  92%, 100% {
    visibility: visible;
    left: 15px;
    bottom: 202px;
  }
}
@-moz-keyframes powerAnim {
  0%, 42% {
    visibility: hidden;
    left: 9px;
    bottom: 197px;
  }
  43% {
    visibility: visible;
    left: 9px;
    bottom: 197px;
  }
  46%, 90% {
    visibility: visible;
    left: 9px;
    bottom: 202px;
  }
  92%, 100% {
    visibility: visible;
    left: 15px;
    bottom: 202px;
  }
}
@-ms-keyframes powerAnim {
  0%, 42% {
    visibility: hidden;
    left: 9px;
    bottom: 197px;
  }
  43% {
    visibility: visible;
    left: 9px;
    bottom: 197px;
  }
  46%, 90% {
    visibility: visible;
    left: 9px;
    bottom: 202px;
  }
  92%, 100% {
    visibility: visible;
    left: 15px;
    bottom: 202px;
  }
}
@keyframes powerAnim {
  0%, 42% {
    visibility: hidden;
    left: 9px;
    bottom: 197px;
  }
  43% {
    visibility: visible;
    left: 9px;
    bottom: 197px;
  }
  46%, 90% {
    visibility: visible;
    left: 9px;
    bottom: 202px;
  }
  92%, 100% {
    visibility: visible;
    left: 15px;
    bottom: 202px;
  }
}
#powerLed {
  position: absolute;
  background-color: #202020;
  border-radius: 2px;
  width: 4px;
  height: 4px;
  left: 10px;
  bottom: 180px;
  -webkit-animation: powerLedAnim 5s forwards;
  -moz-animation: powerLedAnim 5s forwards;
  -o-animation: powerLedAnim 5s forwards;
  animation: powerLedAnim 5s forwards;
}

@-webkit-keyframes powerLedAnim {
  0%, 49% {
    width: 0px;
    height: 0px;
    left: 12px;
    bottom: 172px;
  }
  52%, 91% {
    background-color: #202020;
    width: 4px;
    height: 4px;
    left: 10px;
    bottom: 170px;
  }
  92%, 100% {
    background-color: red;
    width: 4px;
    height: 4px;
    left: 10px;
    bottom: 170px;
  }
}
@-moz-keyframes powerLedAnim {
  0%, 49% {
    width: 0px;
    height: 0px;
    left: 12px;
    bottom: 172px;
  }
  52%, 91% {
    background-color: #202020;
    width: 4px;
    height: 4px;
    left: 10px;
    bottom: 170px;
  }
  92%, 100% {
    background-color: red;
    width: 4px;
    height: 4px;
    left: 10px;
    bottom: 170px;
  }
}
@-ms-keyframes powerLedAnim {
  0%, 49% {
    width: 0px;
    height: 0px;
    left: 12px;
    bottom: 172px;
  }
  52%, 91% {
    background-color: #202020;
    width: 4px;
    height: 4px;
    left: 10px;
    bottom: 170px;
  }
  92%, 100% {
    background-color: red;
    width: 4px;
    height: 4px;
    left: 10px;
    bottom: 170px;
  }
}
@keyframes powerLedAnim {
  0%, 49% {
    width: 0px;
    height: 0px;
    left: 12px;
    bottom: 172px;
  }
  52%, 91% {
    background-color: #202020;
    width: 4px;
    height: 4px;
    left: 10px;
    bottom: 170px;
  }
  92%, 100% {
    background-color: red;
    width: 4px;
    height: 4px;
    left: 10px;
    bottom: 170px;
  }
}
#speakerFilter {
  position: absolute;
  background-color: #555;
  border-radius: 2px;
  width: 4px;
  height: 4px;
  left: 95px;
  bottom: 20px;
  visibility: visible;
  box-shadow: 3px -3px #555, 6px -6px #555, 9px -9px #555, 0px 6px #555,3px 3px #555, 6px 0px #555, 9px -3px #555, 12px -6px #555, 15px -9px #555, 3px 9px #555, 6px 6px #555, 9px 3px #555, 12px 0px #555, 15px -3px #555, 18px -6px #555, 9px 9px #555, 12px 6px #555, 15px 3px #555, 18px 0px #555;
  -webkit-animation: speakerFilterAnim 5s forwards;
  -moz-animation: speakerFilterAnim 5s forwards;
  -o-animation: speakerFilterAnim 5s forwards;
  animation: speakerFilterAnim 5s forwards;
}

@-webkit-keyframes speakerFilterAnim {
  0%, 75% {
    visibility: hidden;
  }
  76%, 100% {
    visibility: visible;
  }
}
@-moz-keyframes speakerFilterAnim {
  0%, 75% {
    visibility: hidden;
  }
  76%, 100% {
    visibility: visible;
  }
}
@-ms-keyframes speakerFilterAnim {
  0%, 75% {
    visibility: hidden;
  }
  76%, 100% {
    visibility: visible;
  }
}
@keyframes speakerFilterAnim {
  0%, 75% {
    visibility: hidden;
  }
  76%, 100% {
    visibility: visible;
  }
}


</style>

---

## File: website/aipolicy.md

# AI Coding Assistants

This document provides guidance for AI tools and developers using AI
assistance when contributing to gbdev ([github.com/gbdev](https://github.com/gbdev)) projects.

This is the **generic, gbdev-wide** AI policy. Individual projects under the gbdev umbrella may define their own policy, which takes precedence over this document; when present, such project-specific policies can be found in the repository's `CONTRIBUTING.md` (except for the attribution/disclosure requirement below, which is mandatory and cannot be overridden).

::: tip Policy

**LLM-assisted contributions are accepted but discouraged, provided they are disclosed
as described in [Attribution](#attribution)**.

:::


Some projects may not agree with this generic policy at all, and may reject AI-assisted contributions entirely. Even where a project's policy allows AI-assisted contributions, an individual maintainer may still decline to review a specific PR on that basis, at their discretion.

gbdev as a community does not endorse or recommend the use of generative AI
assistants for software development, as it raises multiple concerns about ethics,
legality, copyright, etc.

Nevertheless, gbdev acknowledges that these practices are already in use and
likely here to stay. Rather than banning their use, the project chooses to place responsibility
on contributors and therefore defines the following guidelines:

Contributions to gbdev projects are welcome, but should be your own work.
Anything you submit is your responsibility, even if you used AI or other tool assistance to
help create it. You must explain which tools you used and what
they did (like citing sources for written work). You must have sufficiently evaluated and
properly understood the work you intend to submit, and be able to explain and defend it.

Contributors MUST NOT submit "unattended" LLM-generated contributions - that is,
prompt-generated contributions that received no further human vetting, and more broadly
any submission produced with little to no meaningful human review, understanding, or
involvement - unless the project has an area explicitly designated for such
contributions. If no such area exists, unattended contributions are forbidden.

Contributors are expected to follow the standard gbdev contribution process and community
guidelines, as well as any project-specific guidelines or rules where applicable.

## Why This Matters

gbdev is more than just code: it is an open source community built on a shared interest
in Game Boy development, and that community grows largely through mentorship. Education and
guidance of new contributors are part of our mission. Maintainers spend
their limited time reviewing new contributors' work not only to get a given change
merged, but to help those contributors become effective, self-sufficient contributors
in their own right (see ["Contributor Poker"](https://kristoff.it/blog/contributor-poker-and-ai/)).

Heavily AI-assisted contributions undermine this. A new contributor submitting
largely LLM-generated output places an outsized review burden on maintainers, who
must scrutinize code the contributor may not fully understand, which can contribute
to reviewer burnout. Just as importantly, a contributor who leans on an LLM instead
of working through a project's internals does not build the understanding needed to
become an effective contributor, so the time invested in reviewing and mentoring
them produces a return for the project that is not only less lasting, but potentially
damaging.

::: tip A Living Document

This policy reflects our current thinking and is not final. AI tooling, community norms, and the legal landscape around generative AI are all still shifting, and we may revise these guidelines as new facts, tools, and experiences warrant. An open RFC can be found [here](https://github.com/gbdev/gbdev.github.io/issues/103).

:::

## Licensing and Legal Requirements

All contributions MUST comply with the licensing terms of the project you are contributing to.
Verify that any AI-generated content does not introduce incompatible or unclear licensing.
Contributors are responsible for ensuring the contribution is clean.

## Commit authorship

AI agents MUST NOT author git commits.

The human submitter is responsible for:

* Reviewing all AI-generated code
* Ensuring compliance with licensing requirements
* Taking full responsibility for the contribution

## Attribution

When AI tools have meaningfully assisted in producing a contribution, it MUST be disclosed with an `Assisted-by` tag in the git commit message, in the following format:

```
  Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2]
```

Where:

* `AGENT_NAME` is the name of the AI tool or framework
* `MODEL_VERSION` is the specific model version used
* `[TOOL1] [TOOL2]` are optional specialized analysis tools used
  (e.g., coccinelle, sparse, smatch, clang-tidy)

Basic development tools (git, gcc, make, editors) should not be listed.

Example:

```
  Assisted-by: Claude:claude-3-opus coccinelle sparse
```

## References

Adapted from [Linux Kernel - AI Coding Assistants](https://docs.kernel.org/process/coding-assistants.html),
some proposals in [General Resolution: LLM usage in Debian](https://www.debian.org/vote/2026/vote_002) and
[SF Conservancy - Recommendations When Using LLM-backed Generative AI Systems for FOSS Contributions](https://sfconservancy.org/llm-gen-ai/llm-backed-generative-ai-recommendations.html).

August 16, 2026 - gbdev AI policy working group.

---

## File: website/chat.md

---
sidebar: false
head:
  - - meta
    - name: og:title
      content: gbdev chat channels
  - - meta
    - name: og:description
      content: Join gbdev.io community channels
---

# Community channels

Join our community on the following channels. Before posting, read the rules and the FAQ (usually in the welcome threads or in the signup).

- [Discord Server](https://discord.gg/RjJKA8wrD4).
- IRC: [`#gbdev` on Libera.chat](https://web.libera.chat/#gbdev) is bridged to the Discord chat.
  - `#gbdev-ot`, `#gbdev-casual`, `#gbdev-hw`, `#gbdev-meta`, `#gbdev-asm`, `#gbdev-gfx`, `#gbdev-emudev`, `#gbdev-romhacking` and `#gbdev-research` are also available for discussion about specific topics.

---

## File: website/contributing.md

# Contribution guidelines

These base guidelines (if not specified otherwise) apply to every repository under the [**gbdev**](https://github.com/gbdev/) github organisation. The specific CONTRIBUTING rules of the repository in object also apply. In case of conflicts, those last ones have the priority.

::: tip Reaching out to maintainers

If you are unsure about anything or need guidance, you can always reach out the maintainers on [Discord or IRC](https://gbdev.io/chat.html).

:::

## AI usage

You're allowed to use AI/LLM tools to help with your contribution, but you must disclose it and meet certain expectations around reviewing and understanding the work you submit.

Unattended AI-driven interactions and contributions are not allowed, unless the project has an area explicitly designated for such contributions.

See the full [AI Coding Assistants policy](aipolicy.md).

## Issues

- Provide an example to reproduce the problem in isolation. This should include no private code. The example should be self-runnable. If a snippet is too small, create a repository.
- Unless the problem is visual, do not include screenshots. They are not helpful.
- Please be patient. You are probably frustrated by your problem - the maintainers are likely not aware of your workflow.
- Be grateful for any help you are getting and follow through. The maintainers are likely giving you support for free.
- Informal conversations are great (e.g. on Discord), but once a problem, feature request etc is outlined, opening an issue formalizing it is the most important thing you can do to help this information flow.
  - Open an issue in the related repository. If you don't know where it's better suited, [ask for help](https://gbdev.io/chat.html).
  - Include context and any information useful to understand the situation. Don't be scared about doing something wrong or _not good enough_, maintainers will follow up if needed.

## Pull Requests

- Keep titles and commit messages brief and concise.
- If the PR is not self-explanatory, add brief description of what has been done and implementation choices (if any).
- Keep the work in the branch focused and directed at accomplishing _only_ what you are describing in the PR. Ideally a PR should address a single issue (or a set of assimilable ones).
- PRs require commitment and they are an investment by the (volunteer) maintainers. If you don't have time or abandon the PR after reviews have been left and additional changes are needed, other maintainers may take over to make sure the PR can move forward and the work is not lost.
- Maintainers may chime in and commit to your feature branch on minor fixes (or suggestions agreed upon).
- Don't be scared of opening more PRs for the same task, if they can be logically divided. The smaller the changes, the faster they can get reviewed and eventually merged.
- When adding new features, think about tests and possibly updating documentation. Think about other parts of the project which may be affected by the change.
- Document trade-offs, risks, implementation and design choices, if any.

## Maintainers workflow

- Tests and pipelines must pass. If they failed because of something unrelated to the PR (e.g. a link died in the meantime), mention it in the PR thread.
- If the solution is "sub-optimal" (but still better than the actual implementation) and there are clear possible improvements, mention them and try to collaborate with the contributor, but ultimately prefer merging something sub-optimal than keeping the content stale/inactive. An issue can be opened (or kept opened) to report this situation.
- If there's an ongoing conversation/discussion on a PR/thread, let it continue. Don't abruptly merge unless there's a clear consensus among the core maintainers (and approvals).
- Respect the requests from other maintainers for additional time if they ask for it.

  
For disruptive (e.g. major and/or breaking changes) PRs:

- Wait for 2\* maintainers approval
- Every week of inactivity/staleness, ping maintainers
- After **2** weeks from last approval, PR is merge-able with only 1 maintainer approval

For non-disruptive PRs:

- Wait for 1 maintainer approval
- If a maintainer wants to put a veto and to make the PR wait for his/her input after the mentioned period, the "veto" label can be used

\*if there are at least 2 active maintainers, otherwise 1.

## Where can I help?

Here's a list of the project in most need of help:

### GB ASM Tutorial

If you're confident in writing assembly, we need contributors in helping us write, proofread and suggest improvements for new lessons in the tutorial.

Technologies and tools: Assembly, RGBDS, Rust, mdBook.

[Homepage](https://gbdev.io/gb-asm-tutorial) - [Repository](https://github.com/gbdev/gb-asm-tutorial)

### Pan Docs

The single, most comprehensive technical reference to Game Boy available to the public. Here, you can help adding new content, proof reading, fixing typos, preparing updated versions of figures or port new content from old resources.

Technologies and tools: Markdown, Rust, Python, mdBook.

[Homepage](https://gbdev.io/pandocs/) - [Repository](https://github.com/gbdev/pandocs) - [Beginner issues](https://github.com/gbdev/pandocs/issues?q=is%3Aissue+is%3Aopen+label%3AHacktoberfest)

### RGBDS

The de-facto standard assembly toolchain for the Nintendo Game Boy & Game Boy Color.

Technologies and tools: C, C++, Assembly, Rust

[Homepage](https://rgbds.gbdev.io/) - [Repository](https://github.com/gbdev/rgbds) - [Beginner issues](https://github.com/gbdev/rgbds/issues?q=is%3Aissue+is%3Aopen+label%3Ahacktoberfest)

### Homebrew Hub

A community-led attempt to collect, archive and save every unofficial game, homebrew, patch, hackrom for Game Boy produced by the community through decades of passionate work.

Technologies and tools:

- Frontend: Vue JS 3, Nuxt 3
- Backend: Django REST

[Homepage](https://hh.gbdev.io/) - [Repository (backend)](https://github.com/gbdev/homebrewhub) - [Repository (frontend)](https://github.com/gbdev/virens)

### Homebrew Hub Database

This is the database of games powering the main Homebrew Hub project. Find a game (we have a lot to add!) and add it with a Pull Request. Or - even better - write scrapers to dump entire sources and add them to our repository!

Technologies and tools: JSON Schema, Python, Javascript, BeautifulSoup

[Homepage](https://hh.gbdev.io/) - [Repository](https://github.com/gbdev/database) - [Beginner issues](https://github.com/gbdev/database/issues?q=is%3Aissue+is%3Aopen+label%3AHacktoberfest)

## References

[1](https://nitter.net/matteocollina/status/1359087694375174145)

---

## File: website/donate.md

# Donations and bounties

## Donate

You can donate on our [Open Collective](https://opencollective.com/gbdev) or on [GitHub sponsors](https://github.com/sponsors/gbdev). You can send a one-time donation of any amount or join the collective as backer, donating monthly.

::: tip WHY OPENCOLLECTIVE

The [gbdev](https://github.com/gbdev) organisation is an _informal_ collective of individuals. As such, we are not able to hold any fund in our accounts. Instead, the US based non-profit [Open Source Collective](https://www.oscollective.org/) is our [fiscal host](https://thesocialchangeagency.org/blog/what-is-fiscal-hosting/), providing us the financial and legal infrastructure to receive donations and spend money.

:::

It's also possible to donate specifically [towards a project](https://opencollective.com/gbdev/projects) under our umbrella. We'll do our best to honour this preference.

Donations are subject to a 10% fee from our Open Collective host (Open Source Collective) plus a variable fee from the payment processor (Wise).

Money collected on GitHub sponsors are paid out by GitHub to our Open Collective at the end of each month.

## What do we spend the money on?

We mostly use the money to:

-   Organize events (such as the gbcompo21) and give prizes,
-   Fund maintainers and contributors to our repositories,
-   Put bounties on issues on our repositories,
-   Donate back to some of the packages we use,
-   Pay servers and domains.

Expenses are tracked [here](https://opencollective.com/gbdev).

## Sponsoring

If you have a company and you are interested in being highlighted on some of our webpages and channels, we have sponsorship tiers starting at 50$/month. Get in touch at sponsors@gbdev.io to understand if this could be for you. We are interested in hearing how our software is helping your business etc.

Sponsorships are evaluated on a case-to-case basis and only be accepted if we feel like our core values are compatible and the relationship can last in the long term with beneficial effect for both parties.

## Bounties

Sometimes, we may assign a bounty to an Issue. Labels such as `bounty-25$`, `bounty-50$`, `bounty-100$` are used to specify the amount. If you wish to work on a issue with a bounty attached, it's a good measure to comment on the issue asking for it to be assigned to you\*.

If you send a Pull Request fixing that issue and this work gets merged, you will be able to claim the corresponding bounty.

Bounties are paid out from our Open Collective: submit an expense mentioning the Issue you worked on and make sure to use your GitHub email (or the one you authored the git commit with) when creating an account on OpenCollective. Wire transfers and PayPal are supported\*\*.

Details are generally verified in the following 24-48 hours. Payments are then processed by our Open Collective host a couple of times per week.

::: warning TAX CONSIDERATIONS

Make sure to check the tax regulations of your country and properly declare the bounties received. We're not tax consultants and we cannot help with that.

:::

\* In case of inactivity we may remove the assignment to you or let someone else who submitted a PR claim the bounty.

\*\* Our Open Collective host uses Wise as the payment processor so the countries supported for the bank transfers depends on them.

---

## File: website/gbcompo-statement.md

---
head:
  - - meta
    - name: og:title
      content: Statement on the recent removal request and rule compliance in GBCompo events
---

# Statement on the recent removal request and rule compliance in GBCompo events

This statement follows:

- [When Exercising Copyrights Puts a Gamedev Under Threat (My Take on GBCOMPO 25) - allalonegamez](https://web.archive.org/web/20251208215130/https://allalonegamez.itch.io/zoryad/devlog/1135761/when-exercising-copyrights-puts-a-gamedev-under-threat-my-take-on-gbcompo-25)
- [On the gbcompo23 dispute - asie](https://blog.asie.pl/2025/12/gbcompo-2023-dispute/)
- [https://www.reddit.com/r/gbstudio/comments/1pg8r8q/comment/nszxqdn](https://www.reddit.com/r/gbstudio/comments/1pg8r8q/comment/nszxqdn)

## 0. gbdev.io, Homebrew Hub and gbcompo events

[gbdev.io](https://gbdev.io/) is a non-profit initiative that maintains, develops, and promotes open-source tools, documentation, and digital preservation projects for the Game Boy platform. Since 2021 we also host the "gbcompo" events.

[Homebrew Hub](https://hh.gbdev.io/) is gbdev.io's institutional repository for Game Boy homebrew, dedicated to long-term digital preservation. We use it to archive and mirror all GBCompo submissions, so that the versions entered into the competition remain available and accessible over time.

GBCompo events ([2021](https://gbdev.io/gbcompo21.html), [2023](https://gbdev.io/gbcompo23.html), [2025](https://gbdev.io/gbcompo25.html)) are game development competitions organised by gbdev.io, focused on creating new free Game Boy homebrew games. Developers submit games that are judged, showcased, and actively promoted during and after each edition. Some entries later go on to have commercial success, including paid digital releases and physical editions; while we are happy about this and actively showcase it, we do not profit from it, and this is not the focus of the competition.

GBCompo events are run on a non-profit basis under a clear, shared framework. The prize pool is funded entirely by community donations and sponsors, with organisers and judges putting in around six months of unpaid work around and during each edition. No profit is made: donated and sponsored funds are redistributed to winners, with only a small portion going back into gbdev.io’s common funds to help support future events (for example, gbcompo25's first $500 in the prize pool were contributed by gbdev.io).

All the financial statements are public on our [Open Collective pages](https://opencollective.com/gbdev/). 

The competition is centred on the creation and release of free Game Boy homebrew games, with open-source releases encouraged but not required.

This information is available upfront and partecipants are reminded to read about the ruleset when they are submitting.

## 1. Removal of Games from Homebrew Hub

At the explicit request of 'allalonegamez' we have begun removing their games from Homebrew Hub. All copies of the relevant entries hosted on Homebrew Hub are being taken offline (subject only to delays such as DNS propagation, rebuilds, and caching). We are fully complying with their request to remove the content from our platform.

We were requested to take down a game for which the copy on Homebrew Hub was the last available online, for free. This game participated in the 'gbcompo23' competition and was awarded monetary prizes and promotion through gbdev.io channels.

## 2. Competition Rules and Availability of Jam Versions

[Rule 20](https://gbdev.io/gbcompo23.html#general-criteria) of 'gbcompo23', the event where 'allalonegamez' game was submitted and won prizes says:

> The submission must be available for free for the public (and not only the judges). Submission **will be published and kept online for free on the competition website**, while you are free to keep working on it (and eventually charge for it/make commercial usage).

(emphasis added)

We publish the jam submitted ROMs and keep them online for free on on gbdev's Homebrew Hub, after the competition window ends. Additional ZIP compilations are also prepared. **kept online for free** clearly means that the **submitted jam version** must remain publicly accessible after the competition, and not be taken down, delisted, or paywalled at a later date.

In practise though, we don't enforce a specific 'competition website'; any stable, accessible online location is acceptable to be considered in compliance with Rule 20. A request to take down the default mirror to satisfy the rule in other ways/places was acceptable.

We did not want to host a creator's work against their will, even if they had accepted their entry being kept online by submitting to our event, so we accepted a withdrawal scenario where 'allalonegamez' had removed every free copy of the jam version and we complied by removing ours.

> The gbcompo23 entry in question was put offline by the author sometime between [March](https://web.archive.org/web/20240314014850/https://allalonegamez.itch.io/) and [June 2024](https://web.archive.org/web/20240625110157/https://allalonegamez.itch.io/). This is a perfectly legitimate approach, as long as the jam-submitted versions of the games are kept online and available for free, somewhere, as per rules of the event they were submitted to.


## 3. Disqualification and Prize Eligibility

Since the author did not entertain the idea of keeping the copy anywhere else online for free, the entry is in violation of Rule 20.

Because the entry no longer complies to the event rules, they'll be disqualified from the competition. Disqualified entries are not eligible for prizes nor leaderboards.

In order to be fair to all other participants who are respecting the rules and keeping their jam-versions available, any prizes or awards previously assigned to these entries should be reassigned to eligible participants. Any mention of a refund of prizes was made solely in this context of fairness and proper redistribution.

## 4. No Legal action is being taken

We want to clarify that we have no intention of taking legal action or attempting to enforce a prize refund by any means. Our reference to an 'expectation of a refund' was intended purely as a matter of fairness to all parties involved in the event, not as a threat or condition, and we recognize that our wording may have come across more harshly than intended. At no point was legal action discussed or considered, and we did not withhold or delay any part of the removal requested by 'allalonegamez'.

> 'allalonegamez' is [now offering to refund the prize and donate it to a charity](https://old.reddit.com/r/gbstudio/comments/1pg8r8q/when_exercising_copyrights_puts_a_gamedev_under/nsy77rt/). We appreciate the offer, however we are not and have never been interested in pursuing the financial matter further so we do not have a say on that. No further action will be taken in that regard. We are in any case ready to reinstate the entry in its earned position at any time, if and when it becomes compliant with the rules again.

## 5. Fairness to all parties involved in events

The GBCompo prize pool is funded by people who donate money under specific premises: free games will be released in the framework of the ruleset of the event. Participants also agree to abide by these rules as a condition for qualifying for prizes and promotion.

In this context, it would be unfair to continue to award exposure, promotion, and prizes to a participant who is not abiding by the rules, receiving prizes, exposure and promotion only to pull their entry offline and make them paid-only, while many other submitters – including those who did not win – continue to respect them and keep their jam-versions available. 

Disqualification in such a case is necessary to uphold the expectations of all the other actors in the event, including donors, organisers and other participants.

## 6. Next steps

We plan to revise the wording of the rules to make this expectation clearer, explicitly indicating where entries will be kept online and using more precise language about the long-term, effectively perpetual availability of the submitted jam versions.

---

## File: website/gbcompo21.md

---
head:
  - - meta
    - name: og:title
      content: GB Compo 2021
  - - meta
    - name: og:description
      content: >
        An OG game competition! Create original games, demos, homebrews tools and music
        for the original Game Boy / Game Boy Color and compete for glory (and a lot of prizes)!
---

# Game Boy Competition 2021



> *gbcompo21* was a game jam run from July 1 to October 1 2021 and it was hosted on [itch.io/jam/gbcompo21](https://itch.io/jam/gbcompo21).

Create original games, demos, homebrews tools and music for the original Game Boy (Color) and compete for glory (and up to US$&thinsp;3,829 in prizes)!


## Results

Full leaderboards are available [here](#full-results). Top rated entries are playable [here](https://hh.gbdev.io/search?tags=gbcompo21-shortlist) while the complete list can be found on [Homebrew Hub](https://hh.gbdev.io/search?tags=gbcompo21) and [Itch.io](https://itch.io/jam/gbcompo21/entries). A zip file can be downloaded from [GitHub](https://github.com/gbdev/gbcompo21/releases/tag/gbcompo21_entries).

**Games**:

1. [Unearthed](https://hh.gbdev.io/game/unearthed) - BenJelter
2. [Rhythm Land](https://hh.gbdev.io/game/rhythm-land) - martendo
3. [Shock Lobster](https://hh.gbdev.io/game/shock-lobster) - tbsp
4. [Dango Dash](https://hh.gbdev.io/game/dango-dash) - Rebusmind
5. [Rebound](https://hh.gbdev.io/game/rebound) - DevEd
6. [Core Machina](https://hh.gbdev.io/game/core-machina) - Pearacidic
7. [Sushi Nights](https://hh.gbdev.io/game/sushi-nights) - Zalo
8. [MARLA and the Elemental Rings](https://hh.gbdev.io/game/marla-and-the-elemental-rings-demo) - Geek_Joystick
9. [Porklike GB](https://hh.gbdev.io/game/porklike-gb) - binji
10. [GB Corp](https://hh.gbdev.io/game/gb-corp) - Dr. Ludos
11. [Dawn Will Come](https://hh.gbdev.io/game/dawn-will-come) - eishiya
12. [\<corrib75\>](https://hh.gbdev.io/game/corrib75) - gearfo
13. [El Dueloroso](https://hh.gbdev.io/game/el-dueloroso) - Adrián JG
14. [Glory Hunter](https://hh.gbdev.io/game/glory-hunters) - 2think design studio
15. [Renegade rush](https://hh.gbdev.io/game/renegade-rush) - quinnp
16. [Fix My Heart](https://hh.gbdev.io/game/fix-my-heart) - Filipe Bianchin
17. [GBCspelunky](https://hh.gbdev.io/game/gbcspelunky) - sukus

Winning entry for the **Music** category: [Zilogized](https://hh.gbdev.io/game/zilogized) - Kabcorp

Winning entry for the **Tools** category: [Brainfox](https://hh.gbdev.io/game/brainfox) - Yprit


## Sponsors

::: warning
Donations to the prize pool are now __closed__, no more are being accepted.
:::

The entire prize pool is community-funded. Donate or sponsor the competition through [GitHub sponsors](https://github.com/sponsors/avivace?editing=true&frequency=one-time).

[ ](https://incube8games.com/pages/developers)

[ ](https://greenboygames.com/)
<a href="https://catskullelectronics.com/collections/game-boy" target="_blank">
</a>
[ ](https://megacatstudios.com/)
[ ](http://www.bitmapsoft.co.uk/)
[ ](https://firstpressgames.com)


### Donations from Individuals

[Toxa](https://github.com/untoxa), 
[Carles Castillo](https://github.com/Nicolastriplec), 
[Dave VanEe](https://github.com/tbsp), 
[Chris Maltby](https://github.com/chrismaltby), 
[William Bettridge-Radford](https://github.com/willbr), 
[Josh Frisby](https://github.com/oshf),
[Sanqui](https://github.com/Sanqui)

### Partners

[ ](https://gbstudiocentral.com/)[ ](https://www.gbstudio.dev/) [ ](https://www.youtube.com/channel/UCG-geFUwAAnYCZtOkM4PtmA)

<hr>

## Community

Come join us [on Discord or others](https://gbdev.io/chat) for help getting started, and head over to [gbdev.io](https://gbdev.io) for useful links & resources!

We'd love to hear from participants, share your progress on Twitter using the `#gbcompo` hashtag and join the conversation the on Discord `#compo-chat` channel!

## Resources

Here a curated list tools and documentaiton to get you started.

- General links
   - [Pan Docs](https://gbdev.io/pandocs), Game Boy technical documentation
   - [Awesome Game Boy Development list of resources](https://gbdev.io/list)
     - Especially relevant is the ["Software Development"](https://gbdev.io/list.html#software-development) paragraph
   - [How to choose between C and ASM for GB?](https://gbdev.io/guides/tools.html#languages)
- [RGBDS](https://rgbds.gbdev.io/) (ASM language)
  - [GB ASM Tutorial](https://eldred.fr/gb-asm-tutorial)
- [GBDK](https://github.com/gbdk-2020/gbdk-2020/) (C language)
   - [API docs: Getting Started](https://gbdk-2020.github.io/gbdk-2020/docs/api/docs_getting_started.html)
   - [Examples](https://github.com/mrombout/gbdk_playground)
   - [Docs, links and tools](https://gbdk-2020.github.io/gbdk-2020/docs/api/docs_links_and_tools.html)
   - [ZGB: a game engine using GBDK](https://github.com/Zal0/ZGB)
   - [Dedicated Discord]()
- [GBStudio](https://www.gbstudio.dev/) (no coding required)
   - [Resources to get started](https://gbstudiocentral.com/resources/)
   - [Dedicated Discord](https://discord.gg/knRryZWGcm)


## Prize pool

The current prize pool is  and will be split according to [this document](https://docs.google.com/spreadsheets/d/196vIZGLbSk1ewzRnZc7Dy_LJdPQpfJUg-udT4iyBVOg).

Entries will compete in 4 separate categories and 4 rankings will be drawn up. Based on these leaderboards, the top entries will be awarded as follows:

- Best games (80% of the prize pool)
  - The best 5 games will get a prize.
  - The best 3 OSS games will get an additional prize (can overlap)
- Best homebrew/tool (1) (running on a GB/GBC)
- Best music cartridge (1)
- Best demo (1) (demoscene style demos)

The rankings are decided according to the [JUDGING](#JUDGING) criteria.
 
### Open Source Bonus

If you publish your game source code and assets on a public repository (e.g. GitHub) under [an open source license](https://choosealicense.com/) (GPL, MIT, Apache, CC0...) you get **20%** more of the prize as a bonus!

*Money prizes are subject to fees and conversions. They may be delivered as gift cards of your choice.*

## Rules

1. Final entry "deliverable" must consist of a GB or GBC ROM file. You can also attach descriptive documents in TXT, Markdown or HTML file format (e.g. game manual).
2. The ROM file will be tested on real hardware (usually a DMG or a GBC) and on the BGB emulator. In case of different results, the hardware test will be the trusted one.
3. GBC features are allowed.
4. SGB features are allowed. The entries will be evaluated mainly on Game Boy (Color). You can use SGB features, but don't rely on them as main features for your entry.
5. Teams are allowed (award will be split).
6. The competition is focused on *games*, but you are allowed to submit *homebrew tools*, *demoscene style demos* and *music cartridges*. There may be special category prizes for those. There are no preferred themes.
7. ROM Hacks are not allowed.
8. NSFW content is not allowed. Please keep it safe for work and avoid anything overly edgy or distasteful. 
9. You can submit more than one entry. They will be rated separately, but each person or team may only accept prizes on behalf of a single entry (e.g., you can't win both the 1st and 2nd place prize).
10. Judges can submit entries, but they are not eligible for prizes.
11. Only some official MBC chips (MBC1, MBC2, MBC3 and MBC5, as well as no MBC at all) are allowed. The only exceptions are:
    1. While Nintendo only manufactured very specific MBC+ROM combinations, we will allow any power-of-two ROM size between 32 KiB and the maximum for the chosen chip. (This is simply a convenience feature, as any ROM image can be extended to fit a larger ROM chip by appending copies of itself.)
    2. The ROM+RAM combination (i.e., without an MBC, up to 32 KiB ROM and 8 KiB SRAM), with or without battery, will be accepted for compatibility, as many people have simple flash carts that do have SRAM but don't have an MBC. (This can be trivially emulated by any MBC1, MBC3 or MBC5 configuration.)
    3. The variants MBC30 and MBC1M are accepted, as they have been used in licensed games.
    4. Some official MBCs added extra components to the cart: MBC3/MBC30 carts could have an RTC, and MBC5 carts could have rumble. These combinations, and only these combinations, will be accepted. In particular, this means it's impossible to have RTC and rumble in the same cart.
  
12. Entries cannot rely on extra hardware or add-on devices, they must be playable without a specific setup.
13. As long as you produce a GB or GBC ROM file that runs on GB/GBC, any tool to develop the entry is allowed. This includes RGBDS (ASM), GBDK (C), ZGB (C), GB Studio, ...
14. Source code is optional, but very much appreciated. Entries submitted with source code (public repositories on GitHub/GitLab/... are accepted) and with an open source license (See rule 15) are eligible for **additional** prizes. Make sure to fill the "Open Source repository" field with a valid and public repository when submitting on itch.io. See [this issue](https://github.com/gbdev/gbcompo21/issues/3) for further details.
15. To be eligible for the OSS bonus, the code must be licensed under an "Open Source" license (any of the "Free" license listed [here](https://www.gnu.org/licenses/license-list.en.html) is accepted). Assets can be licensed under any of the CC licenses. Obfuscated code, non-reproducible builds, intentionally making the source code hard to read/re-use will exclude the entry form the OSS leaderboards. This is at discretion of the hosts and judges. We must be able to read the code and compile it into a working ROM, running a script or following istructions.
16. The entry must be submitted on the itch.io jam page.
17. Your work must be new and original. You cannot enter with something you were already working on before the start of the jam. The majority of work should be done during the jam. If in doubt, please ask us before proceeding.  
    Examples of things that are **allowed:**  
    - A brand new game using your own (or any) existing game engine.
    - A remake of your (or someone else's) game for a different platform, but with new code and assets.
    - A brand new game you barely started (e.g. you only made a title screen or a mockup of the gameplay).
    - Using templates, tutorial code as starting point (when they're license allow to do so, e.g. [gb-boilerplate](https://github.com/ISSOtm/gb-boilerplate))
    - Asset packs for art and music are ok if the licensing permits. Be sure to make note of what is being reused and under what terms. Use of pre-made assets (vs original ones) may be factored in when judges assess points for relevant criteria (e.g. graphics, audio).
    
    
    Examples of things that are **not allowed:**  
    - A project you've been working on for months.
    - An update/patch for a game you already released publicly.
18. The submission must be available for free for the public (and not only the judges). Submission will be published and kept online for free on the competition website, while you are free to keep working on it (and eventually charge for it/make commercial usage).
19. Multiple submissions are allowed.
20. If your submission contains text that isn't available in English, some judges might not be able to read that text, and they will judge accordingly.

## Judging

Judges will evaluate submissions using the following criteria:

1. **Gameplay** - How entertaining are the gameplay mechanics? How fun is the game?
2. **Technical** - How innovative is it from a technical perspective? Does it push the hardware to the limit?
3. **Originality** - How refreshing is it from a non-technical perspective? Does it have a unique design / mechanics?
4. **Graphics**  -  How impressive are the art, animation and visual effects?
5. **Audio**  -  How good are the music and sound effects?

Criteria applicability:

- Games are ranked according to all the listed criteria
- demos on 2,3,4,5
- Music entries on 5
- Homebrew/tools entries on 2 and 1 (with gameplay being UX)

The leaderboards are computed by averaging the "Overall" score.

## FAQ

Something not clear? Open an issue or join the discussion on Discord.

## Credits

Special thanks to:

- The organizers, from all over the gbdev community: [avivace](https://github.com/avivace), [Proximity Sound](https://proximitysound.com), [untoxa](https://github.com/untoxa), [bbbbbr](https://github.com/bbbbbr), [Sanqui](https://github.com/Sanqui), [aaaaaa123456789](https://github.com/aaaaaa123456789), [ISSOtm](https://github.com/ISSOtm)
- [Optix](https://github.com/Hacktix), for the awesome logo and the other graphical assets
- Judges: ISSOtm, avivace, Sanqui, Asobi.tech, untoxa, Veund, duodreamer

## Full results

### Music

**Winner**: Zilogized by Kabcorp

### Tools

**Winner**: BrainFox by Yprit

### Overall

Sorted by "Overall" score, only the shortlisted entries were ranked by judges.

|Name                         |Gameplay          |Technical         |Originality       |Graphics          |Audio             |**Overall**       |
|-----------------------------|------------------|------------------|------------------|------------------|------------------|------------------|
|Unearthed                    |4.29              |3.86              |4.86              |4.43              |3.29              |4.15              |
|Rhythm Land                  |3.71              |3.71              |4.14              |3.86              |4.57              |4.0               |
|Shock Lobster                |4.0               |4.29              |4.71              |3.71              |3.14              |3.97              |
|Dango Dash                   |3.86              |3.29              |3.86              |4.0               |4.57              |3.92              |
|Rebound                      |4.0               |3.86              |3.57              |3.71              |4.29              |3.89              |
|Core Machina                 |3.14              |3.43              |4.0               |4.57              |4.14              |3.86              |
|Sushi Nights                 |3.86              |3.86              |4.0               |3.71              |3.71              |3.83              |
|MARLA and the Elemental Rings|3.29              |3.86              |3.43              |4.0               |3.86              |3.69              |
|Porklike GB                  |4.0               |4.0               |3.14              |3.57              |3.71              |3.68              |
|GB Corp.                     |3.14              |3.86              |4.57              |3.14              |3.29              |3.6               |
|Dawn Will Come               |2.71              |3.14              |4.29              |4.29              |3.43              |3.57              |
|\<corrib75\>                 |3.71              |3.86              |3.14              |3.71              |3.29              |3.54              |
|El Dueloroso                 |3.29              |3.57              |3.86              |3.0               |3.29              |3.4               |
|Glory Hunters                |3.43              |3.14              |3.43              |3.57              |3.43              |3.4               |
|Renegade rush                |3.57              |3.57              |3.29              |3.0               |3.29              |3.34              |
|Fix My Heart                 |3.71              |2.86              |3.57              |2.71              |2.71              |3.11              |
|GBCspelunky                  |3.0               |3.71              |2.43              |3.71              |2.57              |3.08              |

### Gameplay

|Name                         |Gameplay|
|-----------------------------|--------|
|Unearthed                    |4.29    |
|Rebound                      |4.0     |
|Porklike GB                  |4.0     |
|Shock Lobster                |4.0     |
|Dango Dash                   |3.86    |
|Sushi Nights                 |3.86    |
|Rhythm Land                  |3.71    |
|\<corrib75\>                 |3.71    |
|Fix My Heart                 |3.71    |
|Renegade rush                |3.57    |
|Glory Hunters                |3.43    |
|MARLA and the Elemental Rings|3.29    |
|El Dueloroso                 |3.29    |
|Core Machina                 |3.14    |
|GB Corp.                     |3.14    |
|GBCspelunky                  |3.0     |
|Dawn Will Come               |2.71    |


### Technical

|Name                         |Technical|
|-----------------------------|---------|
|Shock Lobster                |4.29     |
|Porklike GB                  |4.0      |
|Sushi Nights                 |3.86     |
|\<corrib75\>                 |3.86     |
|GB Corp.                     |3.86     |
|MARLA and the Elemental Rings|3.86     |
|Unearthed                    |3.86     |
|Rebound                      |3.86     |
|Rhythm Land                  |3.71     |
|GBCspelunky                  |3.71     |
|El Dueloroso                 |3.57     |
|Renegade rush                |3.57     |
|Core Machina                 |3.43     |
|Dango Dash                   |3.29     |
|Dawn Will Come               |3.14     |
|Glory Hunters                |3.14     |
|Fix My Heart                 |2.86     |


### Originality

|Name                         |Originality|
|-----------------------------|-----------|
|Unearthed                    |4.86       |
|Shock Lobster                |4.71       |
|GB Corp.                     |4.57       |
|Dawn Will Come               |4.29       |
|Rhythm Land                  |4.14       |
|Core Machina                 |4.0        |
|Sushi Nights                 |4.0        |
|Dango Dash                   |3.86       |
|El Dueloroso                 |3.86       |
|Rebound                      |3.57       |
|Fix My Heart                 |3.57       |
|MARLA and the Elemental Rings|3.43       |
|Glory Hunters                |3.43       |
|Renegade rush                |3.29       |
|Porklike GB                  |3.14       |
|\<corrib75\>                 |3.14       |
|GBCspelunky                  |2.43       |

### Graphics

|Name                         |Graphics|
|-----------------------------|--------|
|Core Machina                 |4.57    |
|Unearthed                    |4.43    |
|Dawn Will Come               |4.29    |
|Dango Dash                   |4.0     |
|MARLA and the Elemental Rings|4.0     |
|Rhythm Land                  |3.86    |
|\<corrib75\>                 |3.71    |
|GBCspelunky                  |3.71    |
|Rebound                      |3.71    |
|Shock Lobster                |3.71    |
|Sushi Nights                 |3.71    |
|Glory Hunters                |3.57    |
|Porklike GB                  |3.57    |
|GB Corp.                     |3.14    |
|El Dueloroso                 |3.0     |
|Renegade rush                |3.0     |
|Fix My Heart                 |2.71    |


### Audio

|Name                         |Audio|
|-----------------------------|-----|
|Dango Dash                   |4.57 |
|Rhythm Land                  |4.57 |
|Rebound                      |4.29 |
|Core Machina                 |4.14 |
|MARLA and the Elemental Rings|3.86 |
|Porklike GB                  |3.71 |
|Sushi Nights                 |3.71 |
|Glory Hunters                |3.43 |
|Dawn Will Come               |3.43 |
|El Dueloroso                 |3.29 |
|Renegade rush                |3.29 |
|Unearthed                    |3.29 |
|\<corrib75\>                 |3.29 |
|GB Corp.                     |3.29 |
|Shock Lobster                |3.14 |
|Fix My Heart                 |2.71 |
|GBCspelunky                  |2.57 |

---

## File: website/gbcompo23.md

---
head:
  - - meta
    - name: og:title
      content: GB Compo 2023
  - - meta
    - name: og:description
      content: >
        Join us for an epic game development event celebrating the classics! Show off your creativity and skills by crafting original games for the iconic Game Boy!
---

# Game Boy Competition 2023




> *gbcompo23* was a game jam run from **June 15th** to **September 15**, 2023 and it was hosted on [itch.io/jam/gbcompo23](https://itch.io/jam/gbcompo23).


#### Join us for an epic game development event celebrating the classics! Show off your creativity and skills by crafting original games, demos, homebrew tools or music specifically designed for the legendary Game Boy. Get ready to battle it out for fame, honor, and an incredible array of prizes that await the best entries.



Theme: _You are the monster_.

## Prizes and Categories

The total prize pool of  will be split according to [this document](https://docs.google.com/spreadsheets/d/e/2PACX-1vRTOM0XElTJJGF97GyxzL393SBuPAjW3woxYZo5Fs_X_tC8Yxq_hojVNw74XGeOFYHu0_5bwS3NyQLZ/pubhtml). Anybody can contribute by [donating](https://opencollective.com/gbdev/projects/gbcompo23).

An entry can compete in one and only one of the following leaderboards:

- Games (80% of the prize pool)
  - The best **5** games will get a prize.
  - The best **3** OSS games will get an **additional** prize (can overlap)
  - Examples: [Dango Dash](https://hh.gbdev.io/game/dango-dash), [Core Machina](https://hh.gbdev.io/game/core-machina), [Unearthed](https://hh.gbdev.io/game/unearthed)
- Music cartridges (**1 prize**)
  - Examples: [Zilogized](https://hh.gbdev.io/game/zilogized), [Freebie-gbs-2018](https://hh.gbdev.io/game/freebie-gbs-2018)
- Demoscene style (**1 prize**)
  - This category is for audio-visual demonstrations that run without viewer interaction, in such a way as to demonstrate skill in manipulation of the Game Boy hardware through code, special effects, art, music, and direction. More on [Wikipedia](https://en.wikipedia.org/wiki/Demoscene).
  - Examples: [Back to Color](https://hh.gbdev.io/game/back-to-color), [Is that a demo in your pocket?](https://hh.gbdev.io/game/is-that-a-demo-in-your-pocket), [Cute Demo CGB](https://hh.gbdev.io/game/cute-demo-cgb).
- Tool (**1 prize**)
  - Examples: [Bannerprinter](https://hh.gbdev.io/game/bannerprinter), [On-Board GB Tile Creator](https://hh.gbdev.io/game/obgbtc), [StopWatch](https://hh.gbdev.io/game/stopwatch-version-1).

The rankings are decided according to the [judging](#judging) criteria.

## Community

Come join us [on Discord](/chat.html) for help getting started (or find a team), and head over to [gbdev.io](https://gbdev.io) for a curated list of links and development resources!

We'd love to hear from participants, share your progress using the `#gbcompo23` hashtag and join the conversation on the Discord `#gbcompo23` channel!

We're also on [Mastodon](https://mastodon.social/@gbdev).

## Resources

Here is a curated list tools and documentation to get you started.

- General links
  - [The Game Boy, a hardware autopsy](https://www.youtube.com/playlist?list=PLu3xpmdUP-GRDp8tknpXC_Y4RUQtMMqEu) (Video)
  - [The Ultimate Game Boy Talk](https://media.ccc.de/v/33c3-8029-the_ultimate_game_boy_talk) (Video)
  - [Pan Docs](https://gbdev.io/pandocs), Game Boy technical documentation
  - [Awesome Game Boy Development list of resources](https://gbdev.io/resources.html)
    - Especially relevant is the ["Software Development"](https://gbdev.io/resources.html#software-development) section
  - [How to choose between C and ASM for GB?](https://gbdev.io/guides/tools.html#languages)
- [RGBDS](https://rgbds.gbdev.io/) (ASM language)
  - [GB ASM Tutorial](https://eldred.fr/gb-asm-tutorial)
- [GBDK](https://github.com/gbdk-2020/gbdk-2020/) (C language)
  - [API docs: Getting Started](https://gbdk-2020.github.io/gbdk-2020/docs/api/docs_getting_started.html)
  - [Examples](https://github.com/mrombout/gbdk_playground)
  - [Docs, links and tools](https://gbdk-2020.github.io/gbdk-2020/docs/api/docs_links_and_tools.html)
  - [ZGB: a game engine using GBDK](https://github.com/Zal0/ZGB)
  - [Dedicated Discord](https://discord.gg/XCbjCvqnUY)
  - ["How-to make a Gameboy Game"](https://laroldsjubilantjunkyard.com/tutorials/how-to-make-a-gameboy-game/) tutorial series
- [GBStudio](https://www.gbstudio.dev/) (no coding required)
  - [Resources to get started](https://gbstudiocentral.com/resources/)
  - [Dedicated Discord](https://discord.gg/knRryZWGcm)
- [GBSDK](https://github.com/daid/gbsdk) (C + RGBDS ASM, without a standard library)
  - [Documentation](https://daid.github.io/gbsdk/)
  - [Source for a game](https://github.com/QuinnPainter/CrossConnect) and [another game](https://github.com/QuinnPainter/Wyrmhole) built with GBSDK

## Sponsors

- Main sponsor: [Incube8Games](https://incube8games.com/)
- Gold sponsors: [FerranteCrafts](https://ferrantecrafts.com/), [Macho Nacho](https://macho-nacho.com/)
- Silver sponsors: [Bitmap Soft](https://www.bitmapsoft.co.uk/), [Broke Studio](https://www.brokestudio.fr/), [Yastuna Games](https://yastuna-games.com/en/)
- Bronze sponsors: [insideGadgets](https://shop.insidegadgets.com/)

Interested in sponsoring? Shoot us a [DM](https://twitter.com/gbdev0).

## Rules

### Prizes and Judging

1. The submitted entry must implement / interpret the jam theme (applies to Game category only).
2. Multiple submissions are allowed from the same person or team. They will be rated separately, but each person or team may only accept prizes on behalf of a single entry (e.g., you can't win both the 1st and 2nd place prize).
3. Judges can submit entries, but the entries won't be eligible for prizes. Judges won't score the entries they submitted.
4. Source code is optional, but very much appreciated. Entries submitted with source code (public repositories on GitHub/GitLab/... are accepted) and with an open source license (See rule 5) are eligible for **additional** prizes. Make sure to fill the "Open Source repository" field with a valid and public repository when submitting on itch.io. It is NOT necessary for the source to be available *during* the competition period. Repositories/sources just need to be public from the time the event ends. See [this issue](https://github.com/gbdev/gbcompo21/issues/3) for further details. 
5. To be eligible for the OSS bonus, the code must be licensed under an "Open Source" license (any of the "Free" license listed [here](https://www.gnu.org/licenses/license-list.en.html) is accepted). Assets can be licensed under any of the CC licenses. Obfuscated code, non-reproducible builds, intentionally making the source code hard to read/re-use will exclude the entry form the OSS leaderboards. This is at discretion of the hosts and judges. We must be able to read the code and compile it into a working ROM, running a script or following instructions.
7. If your submission contains text that isn't available in English, some judges might not be able to read that, and they will judge accordingly.
8. Shipment of physical prizes may not be available to all countries, they are subject to shipping ability of the prize donors.

### Hardware Criteria

8. Final entry "deliverable" must consist of a GB or GBC ROM file. You can also attach descriptive documents in TXT, Markdown, PDF or HTML file format (e.g. game manual).
9. The ROM file will be tested on real hardware (primarily a GBC) and on the BGB emulator. In case of different results, the hardware test will be the trusted one.
10. GBC features are allowed, and entries may be GBC only (GBC only should be indicated in game for non-GBC hardware or on the entry's page).
11. SGB features are allowed. The entries will be evaluated mainly on Game Boy Color. You can use SGB features, but don't rely on them as main features for your entry.
12. Only some official MBC chips (MBC1, MBC2, MBC3 and MBC5, as well as no MBC at all) are allowed. The only exceptions are:
    1. While Nintendo only manufactured very specific MBC+ROM combinations, we will allow any power-of-two ROM size between 32 KiB and the maximum for the chosen chip. (This is simply a convenience feature, as any ROM image can be extended to fit a larger ROM chip by appending copies of itself.)
    2. The ROM+RAM combination (i.e., without an MBC, up to 32 KiB ROM and 8 KiB SRAM), with or without battery, will be accepted for compatibility, as many people have simple flash carts that do have SRAM but don't have an MBC. (This can be trivially emulated by any MBC1, MBC3 or MBC5 configuration.)
    3. The variants MBC30 and MBC1M are accepted, as they have been used in licensed games.
    4. Some official MBCs added extra components to the cart: MBC3/MBC30 carts could have an RTC, and MBC5 carts could have rumble. These combinations, and only these combinations, will be accepted. In particular, this means it's impossible to have RTC and rumble in the same cart.
13. Entries cannot rely on extra hardware or add-on devices, they must be playable without a specific setup.
14. As long as you produce a GB or GBC ROM file that runs on GB/GBC, any tool to develop the entry is allowed. This includes RGBDS (ASM), GBDK (C), ZGB (C), GB Studio, ...

### General Criteria

15. Teams are allowed (award will be split).
16. ROM Hacks are not allowed.
17. NSFW content is not allowed. Please keep it safe for work and avoid anything overly edgy or distasteful.
18. The entry must be submitted on the itch.io jam page.
19. Your work must be new and original. You cannot enter with something you were already working on before the start of the jam. The majority of work should be done during the jam. If in doubt, please ask us before proceeding.

    Examples of things that are **allowed:**

    - A brand new game using your own (or any) existing game engine.
    - A remake of your (or someone else's) game for a different platform, but with new code and assets.
    - A brand new game you barely started (e.g. you only made a title screen or a mockup of the gameplay).
    - Using templates, tutorial code as starting point (when they're license allow to do so, e.g. [gb-boilerplate](https://github.com/ISSOtm/gb-boilerplate))
    - Asset packs for art and music and covers of other music are allowed if the licensing permits. Be sure to make note of what is being reused or re-interpreted and under what terms. Use of pre-made assets and content (vs wholly original) may be factored in when judges assess points for relevant criteria (e.g. graphics, audio).

    Examples of things that are **not allowed:**

    - A project you've been working on for months.
    - An update/patch for a game you already released publicly.

20. The submission must be available for free for the public (and not only the judges). Submission will be published and kept online for free on the competition website, while you are free to keep working on it (and eventually charge for it/make commercial usage).
21. It is allowed to submit an entry to multiple events.

## Judging

A team of (~10) judges has been selected before the start of the competition.

Every judge will be asked to compile an unranked shortlist of their favourite entries. Once the shortlists are ready from all the judges, the top 15 most selected entries will be evaluated by the judges on the following criteria:

1. **Gameplay** - How entertaining are the gameplay mechanics? How fun is the game?
2. **Technical** - How innovative is it from a technical perspective? Does it push the hardware to the limit?
3. **Originality** - How refreshing is it from a non-technical perspective? Does it have a unique design / mechanics?
4. **Graphics** - How impressive are the art, animation and visual effects?
5. **Audio** - How good are the music and sound effects?
6. **Theme** - How was the theme implemented in the gameplay, the mechanics and/or the story?

Applicability may vary depending on the category of the entry (e.g. a "Music" ROM may be evaluated only on 5).

The final leaderboard is computed by averaging the "Overall" score.

::: warning
Judges are volunteers. Those details can change without prior notice, as the judging process is long and involves hundreds of entries. Detailed criteria leaderboards will be provided only for entries in the final shortlist.
:::

## Acknowledgements

Special thanks to everyone who helped organising and running this event:

**Donators:** Anonymous Donor

**Partners:** gbdev.io, LaroldsJubilantJunkyard, RetroBreak, GB Studio Central

**Organisers:** avivace, Tronimal, duodreamer, bbbbbr, ISSOtm, RetroBreak, LaroldsJubilantJunkyard, ProximitySound, Hacktix

**Judges:** avivace, ISSOtm, Duo, Tronimal, Ferrante Crafts, NickWestwood, Veund, Toxa

## Past editions

- [GB Competition 2021](https://itch.io/jam/gbcompo21)


## Results

You can find [all submissions in Homebrew Hub](https://hh.gbdev.io/search?tags=event:gbcompo23) and in [this GitHub repository](https://github.com/gbdev/gbcompo23/).

### Music

1. [8bit Sunset (GameBoy Music)](https://hh.gbdev.io/game/kabcorp__8bit-sunset) - Kabcorp
2. [Gb Compo 23 – Music Cart – Tune In!](https://hh.gbdev.io/game/beatscribe__gb-compo-23-music-cart-tune-in) - beatscribe
3. [Monkeys on Mars](https://hh.gbdev.io/game/chavezfunktion__monkeys-on-mars) - Chavez.funktion
4. shared by
   * [The Infernodome – Original GB SongROM!](https://hh.gbdev.io/game/emperorjub__infernodome-song-rom) - EmperorJub
   * [No time for art sorry (Music Cart)](https://hh.gbdev.io/game/incognitiouwu__no-time-for-art-sorry-music-cart) - incognitio
   * [Re: Cycle](https://hh.gbdev.io/game/sloopygoop__re-cycle) - sloopygoop

### Demoscene

1. [Did Somebody Say Demo?](https://hh.gbdev.io/game/vl2m-studio__dssd) - VL2MSTUDIO
2. [DDDDDEEMO(N)](https://hh.gbdev.io/game/lillie-chippie__dddddeemon) - Lillie_chippie

### Tools

1. [Monster Orc-arina](https://hh.gbdev.io/game/pearacidic__monster-orc-arina-a-game-boy-tool) - Pearacidic
2. [Dungeon Master](https://hh.gbdev.io/game/joaobapt__dungeon-master) - joaobapt
3. [WeekPlanner](https://hh.gbdev.io/game/creativags__weekplanner) - CreativaGS
4. shared by
   * [Iron Cor – Stainless](https://hh.gbdev.io/game/elvies__iron-cor-stainless) - Elvies
   * [Nyan note](https://hh.gbdev.io/game/djarky__nyan-note) - Arky750
   * [witch sound tracker](https://hh.gbdev.io/game/djarky__witch-sound-tracker) - Arky750
   * [SGB SOU_TRN demo](https://hh.gbdev.io/game/cloudscomputing__sgb-sou-trn-demo) - cloudscomputing

### Games

#### Overall

|#  |Name                                                                               |Overall|Gameplay|Technical|Originality|Graphics|Audio|Theme|
|--:|-----------------------------------------------------------------------------------|-------|--------|---------|-----------|--------|-----|-----|
|1  |[Hermano](https://hh.gbdev.io/game/patmorita__hermano-game-boy) † §                |4.0    |4.17    |4.0      |3.5        |4.33    |3.83 |4.17 |
|2  |[Feed IT Souls](https://hh.gbdev.io/game/gumpyfunction__feed-it-souls) †           |3.92   |4.17    |3.5      |3.83       |3.83    |3.67 |4.5  |
|3  |[Enceladus](https://hh.gbdev.io/game/mrpapshmir__enceladus) †                      |3.75   |3.5     |3.0      |3.83       |3.67    |3.67 |4.83 |
|5  |[NUNYA](https://hh.gbdev.io/game/bard-0__nunya) †                                  |3.58   |3.33    |3.0      |3.17       |4.33    |3.67 |4.0  |
|6  |[Ghost of the Arcade](https://hh.gbdev.io/game/playinstinct__ghost-of-the-arcade)  |3.42   |3.33    |3.5      |3.33       |3.67    |3.33 |3.33 |
|7  |[Kaiju Kai Kai](https://hh.gbdev.io/game/sunnychowtheguy__kaijukaikai)             |3.33   |3.0     |2.67     |3.5        |3.5     |2.83 |4.5  |
|8  |[Chantey (Prologue)](https://hh.gbdev.io/game/gortyncode__chantey-prologue)        |3.25   |2.83    |3.0      |3.67       |3.83    |4.5  |1.67 |
|9  |[Slime Trials](https://hh.gbdev.io/game/canight__slime-trials) §                   |3.06   |3.83    |3.17     |3.67       |2.33    |2.83 |2.5  |
|10 |[Hidden Gems](https://hh.gbdev.io/game/rebusmind__hidden-gems)                     |3.06   |4.0     |3.0      |4.5        |2.67    |3.0  |1.17 |
|11 |[Abducted](https://hh.gbdev.io/game/grimmrobegames__abducted) §                    |2.94   |2.83    |3.17     |3.83       |3.83    |1.17 |2.83 |
|12 |[Slayer the Hawk](https://hh.gbdev.io/game/mindbleach__slayer-the-hawk)            |2.86   |2.67    |3.5      |4.17       |2.67    |1.5  |2.67 |
|13 |[EXTERMINATOR](https://hh.gbdev.io/game/glitchytsp__exterminator)                  |2.83   |2.67    |2.67     |3.17       |3.0     |2.83 |2.67 |
|14 |[Imperium Strike Force](https://hh.gbdev.io/game/orlando64__imperium-strike-force) |2.69   |3.17    |2.83     |2.33       |3.17    |2.67 |2.0  |
|15 |[Lightseeker](https://hh.gbdev.io/game/zanipixels__lightseeker)                    |2.67   |2.17    |2.17     |3.0        |4.0     |3.17 |1.5  |

Tiebreaks (4-5, 9-10) were explicitly resolved by judges for this leaderboard.

Entries marked with † are awarded the "Best Games" prizes and ones with § get the "Best Open Source Games".

#### Gameplay

|#                     |Name |Score|
|---------------------:|-----|-----|
|1                     | Feed IT Souls | 4.17|
|2                     | Hermano | 4.17|
|3                     | Hidden Gems | 4.0 |
|4                     | Slime Trials | 3.83|
|5                     | Enceladus | 3.5 |
|6                     | Ghost of the Arcade | 3.33|
|7                     | NUNYA | 3.33|
|9                     | Imperium Strike Force | 3.17|
|10                    | Kaiju Kai Kai | 3.0 |
|11                    | Abducted | 2.83|
|12                    | Chantey (Prologue) | 2.83|
|13                    | EXTERMINATOR | 2.67|
|14                    | Slayer the Hawk | 2.67|
|15                    | Lightseeker | 2.17|

#### Technical

|#                     |Name |Score|
|---------------------:|-----|-----|
|1                     | Hermano | 4.0 |
|2                     | Feed IT Souls | 3.5 |
|3                     | Ghost of the Arcade | 3.5 |
|4                     | Slayer the Hawk | 3.5 |
|5                     | Slime Trials | 3.17|
|6                     | Abducted | 3.17|
|7                     | Enceladus | 3.0 |
|8                     | NUNYA | 3.0 |
|10                    | Hidden Gems | 3.0 |
|11                    | Chantey (Prologue) | 3.0 |
|12                    | Imperium Strike Force | 2.83|
|13                    | Kaiju Kai Kai | 2.67|
|14                    | EXTERMINATOR | 2.67|
|15                    | Lightseeker | 2.17|

#### Originality

|#                     |Name |Score|
|---------------------:|-----|-----|
|1                     | Hidden Gems | 4.5 |
|2                     | Slayer the Hawk | 4.17|
|3                     | Enceladus | 3.83|
|4                     | Feed IT Souls | 3.83|
|5                     | Abducted | 3.83|
|6                     | Slime Trials | 3.67|
|7                     | Chantey (Prologue) | 3.67|
|8                     | Hermano | 3.5 |
|9                     | Kaiju Kai Kai | 3.5 |
|10                    | Ghost of the Arcade | 3.33|
|12                    | NUNYA | 3.17|
|13                    | EXTERMINATOR | 3.17|
|14                    | Lightseeker | 3.0 |
|15                    | Imperium Strike Force | 2.33|


#### Graphics

|#                     |Name |Score|
|---------------------:|-----|-----|
|1                     | Hermano | 4.33|
|2                     | NUNYA | 4.33|
|3                     | Lightseeker | 4.0 |
|4                     | Feed IT Souls | 3.83|
|5                     | Abducted | 3.83|
|6                     | Chantey (Prologue) | 3.83|
|7                     | Enceladus | 3.67|
|8                     | Ghost of the Arcade | 3.67|
|9                     | Kaiju Kai Kai | 3.5 |
|11                    | Imperium Strike Force | 3.17|
|12                    | EXTERMINATOR | 3.0 |
|13                    | Hidden Gems | 2.67|
|14                    | Slayer the Hawk | 2.67|
|15                    | Slime Trials | 2.33|

#### Audio Ranking

|#                     |Name |Score|
|---------------------:|-----|-----|
|1                     | Chantey (Prologue) | 4.5 |
|3                     | Hermano | 3.83|
|4                     | Enceladus | 3.67|
|5                     | Feed IT Souls | 3.67|
|6                     | NUNYA | 3.67|
|7                     | Ghost of the Arcade | 3.33|
|8                     | Lightseeker | 3.17|
|9                     | Hidden Gems | 3.0 |
|10                    | Slime Trials | 2.83|
|11                    | Kaiju Kai Kai | 2.83|
|12                    | EXTERMINATOR | 2.83|
|13                    | Imperium Strike Force | 2.67|
|14                    | Slayer the Hawk | 1.5 |
|15                    | Abducted | 1.17|

#### Theme
|#                     |Name |Score|
|---------------------:|-----|-----|
|1                     | Enceladus | 4.83|
|3                     | Feed IT Souls | 4.5 |
|4                     | Kaiju Kai Kai | 4.5 |
|5                     | Hermano | 4.17|
|6                     | NUNYA | 4.0 |
|7                     | Ghost of the Arcade | 3.33|
|8                     | Abducted | 2.83|
|9                     | EXTERMINATOR | 2.67|
|10                    | Slayer the Hawk | 2.67|
|11                    | Slime Trials | 2.5 |
|12                    | Imperium Strike Force | 2.0 |
|13                    | Chantey (Prologue) | 1.67|
|14                    | Lightseeker | 1.5 |
|15                    | Hidden Gems | 1.17|

---

## File: website/gbcompo25.md

---
head:
  - - meta
    - name: og:title
      content: GB Compo 2025
  - - meta
    - name: og:description
      content: >
        Join us for an epic game development event celebrating the classics! Show off your creativity and skills by crafting original games for the iconic Game Boy!
---

# Game Boy Competition 2025



> *gbcompo25* was a game jam run from **June 1st** to **September 1st**, 2025 and it was hosted on [itch.io/jam/gbcompo25](https://itch.io/jam/gbcompo25).

#### Join us for an epic game development event celebrating the classics! Show off your creativity and skills by crafting original games, demos, homebrew tools or music specifically designed for the legendary Game Boy. Get ready to battle it out for fame, honor, and an incredible array of prizes that await the best entries.

## Prizes and Categories

The total prize pool of  will be split according to [this document](https://docs.google.com/spreadsheets/d/e/2PACX-1vRTOM0XElTJJGF97GyxzL393SBuPAjW3woxYZo5Fs_X_tC8Yxq_hojVNw74XGeOFYHu0_5bwS3NyQLZ/pubhtml). Anybody can contribute by [donating](https://opencollective.com/gbdev/projects/gbcompo25).

An entry can compete in one and only one of the following leaderboards:

- Games (80% of the prize pool)
  - The best **5** games will get a prize.
  - The best **3** OSS games will get an **additional** prize (can overlap)
  - Examples: [Hermano](https://hh.gbdev.io/game/patmorita__hermano-game-boy), [Feed IT Souls](https://hh.gbdev.io/game/gumpyfunction__feed-it-souls), [Enceladus](https://hh.gbdev.io/game/mrpapshmir__enceladus)
- Music cartridges (**1 prize**)
  - Examples: [8bit Sunset](https://hh.gbdev.io/game/kabcorp__8bit-sunset), [Tune In!](https://hh.gbdev.io/game/beatscribe__gb-compo-23-music-cart-tune-in)
- Demoscene style (**1 prize**)
  - This category is for audio-visual demonstrations that run without viewer interaction, in such a way as to demonstrate skill in manipulation of the Game Boy hardware through code, special effects, art, music, and direction. More on [Wikipedia](https://en.wikipedia.org/wiki/Demoscene).
  - Examples: [Back to Color](https://hh.gbdev.io/game/back-to-color), [Is that a demo in your pocket?](https://hh.gbdev.io/game/is-that-a-demo-in-your-pocket), [Cute Demo CGB](https://hh.gbdev.io/game/cute-demo-cgb).
- Tool (**1 prize**)
  - Examples: [Monster Orcarina](https://hh.gbdev.io/game/pearacidic__monster-orc-arina-a-game-boy-tool), [On-Board GB Tile Creator](https://hh.gbdev.io/game/obgbtc), [Witch Sound Tracker](https://hh.gbdev.io/game/djarky__witch-sound-tracker).

The rankings are decided according to the [judging](#judging) criteria.

## Community

Come join us [on Discord](/chat.html) for help getting started (or find a team), and head over to [gbdev.io](https://gbdev.io) for a curated list of links and development resources!

We'd love to hear from participants, share your progress using the `#gbcompo25` hashtag and join the conversation on the Discord `#gbcompo25` channel!

We're also on [BlueSky](https://bsky.app/profile/gbdev.io) and [Mastodon](https://mastodon.social/@gbdev).

## Resources

Here is a curated list tools and documentation to get you started.

- General links
  - [The Game Boy, a hardware autopsy](https://www.youtube.com/playlist?list=PLu3xpmdUP-GRDp8tknpXC_Y4RUQtMMqEu) (Video)
  - [The Ultimate Game Boy Talk](https://media.ccc.de/v/33c3-8029-the_ultimate_game_boy_talk) (Video)
  - [Pan Docs](https://gbdev.io/pandocs), Game Boy technical documentation
  - [Awesome Game Boy Development list of resources](https://gbdev.io/resources.html)
    - Especially relevant is the ["Software Development"](https://gbdev.io/resources.html#software-development) section
  - [How to choose between C and ASM for GB?](https://gbdev.io/guides/tools.html#languages)
- [RGBDS](https://rgbds.gbdev.io/) (ASM language)
  - [GB ASM Tutorial](https://gbdev.io/gb-asm-tutorial/index)
- [GBDK](https://github.com/gbdk-2020/gbdk-2020/) (C language)
  - [API docs: Getting Started](https://gbdk-2020.github.io/gbdk-2020/docs/api/docs_getting_started.html)
  - [Examples](https://github.com/mrombout/gbdk_playground)
  - [Docs, links and tools](https://gbdk-2020.github.io/gbdk-2020/docs/api/docs_links_and_tools.html)
  - [ZGB: a game engine using GBDK](https://github.com/Zal0/ZGB) / [CrossZGB](https://github.com/gbdk-2020/CrossZGB) (an updated fork of ZGB)
  - [Dedicated Discord](https://discord.gg/XCbjCvqnUY)
  - ["How-to make a Gameboy Game"](https://laroldsretrogameyard.com/category/tutorials/gb/) tutorial series
- [GBStudio](https://www.gbstudio.dev/) (no coding required)
  - [Resources to get started](https://gbstudiocentral.com/resources/)
  - [Dedicated Discord](https://discord.gg/knRryZWGcm)
- [GBSDK](https://github.com/daid/gbsdk) (C + RGBDS ASM, without a standard library)
  - [Documentation](https://daid.github.io/gbsdk/)
  - [Source for a game](https://github.com/QuinnPainter/CrossConnect) and [another game](https://github.com/QuinnPainter/Wyrmhole) built with GBSDK

## Sponsors

<a target="_blank" href="https://modretro.com/"></a>
[](https://incube8games.com/)
<a target="_blank" href="https://shop.insidegadgets.com/"></a>

## Rules

### JAM THEME: None (do whatever you want!)

### Prizes and Judging

1. Multiple submissions are allowed from the same person or team. They will be rated separately, but each person or team may only accept prizes for a single entry (e.g., you can't win both the 1st and 2nd place prize).
2. Judges can submit entries, but the entries won't be eligible for prizes. Judges won't score the entries they submitted.
3. Source code is optional, but very much appreciated. Entries submitted with source code (public repositories on GitHub/GitLab/... are accepted) and with an open source license (See rule 4) are eligible for **additional** prizes. Make sure to fill the "Open Source repository" field with a valid and public repository when submitting on itch.io. It is NOT necessary for the source to be available *during* the competition period. Repositories/sources just need to be public from the time the event ends. See [this issue](https://github.com/gbdev/gbcompo21/issues/3) for further details. 
4. To be eligible for the OSS bonus, the code must be licensed under an "Open Source" license (any of the "Free" license listed [here](https://www.gnu.org/licenses/license-list.en.html) is accepted). Assets can be licensed under any of the CC licenses. Obfuscated code, non-reproducible builds, intentionally making the source code hard to read/re-use will exclude the entry form the OSS leaderboards. This is at discretion of the hosts and judges. We must be able to read the code and compile it into a working ROM, running a script or following instructions.
5. If your submission contains text that isn't available in English, some judges might not be able to read that, and they will judge accordingly.
6. Shipment of physical prizes may not be available to all countries, they are subject to shipping ability of the prize donors.
7. The submitted entry must implement / interpret the jam theme (applies to Game category only).

### Hardware Criteria

8. Final entry "deliverable" must consist of a GB or GBC ROM file. You can also attach descriptive documents in TXT, Markdown, PDF or HTML file format (e.g. game manual).
9. The ROM file will be tested on real hardware (primarily a GBC) and on a hardware accurate emulator such as BGB and SameBoy. In case of different results, the hardware test will be the trusted one.
10. GBC features are allowed, and entries may be GBC only (GBC only should be indicated in game for non-GBC hardware or on the entry's page).
11. SGB features are allowed. The entries will be evaluated mainly on Game Boy Color. You can use SGB features, but don't rely on them as main features for your entry.
12. Only some official MBC chips (MBC1, MBC2, MBC3 and MBC5, as well as no MBC at all) are allowed. The only exceptions are:
    1. While Nintendo only manufactured very specific MBC+ROM combinations, we will allow any power-of-two ROM size between 32 KiB and the maximum for the chosen chip. (This is simply a convenience feature, as any ROM image can be extended to fit a larger ROM chip by appending copies of itself.)
    2. The ROM+RAM combination (i.e., without an MBC, up to 32 KiB ROM and 8 KiB SRAM), with or without battery, will be accepted for compatibility, as many people have simple flash carts that do have SRAM but don't have an MBC. (This can be trivially emulated by any MBC1, MBC3 or MBC5 configuration.)
    3. The variants MBC30 and MBC1M are accepted, as they have been used in licensed games.
    4. Some official MBCs added extra components to the cart: MBC3/MBC30 carts could have an RTC, and MBC5 carts could have rumble. These combinations, and only these combinations, will be accepted. In particular, this means it's impossible to have RTC and rumble in the same cart.
13. Entries cannot rely on extra hardware or add-on devices, they must be playable without a specific setup.
14. As long as you produce a GB or GBC ROM file that runs on GB/GBC, any tool to develop the entry is allowed. This includes RGBDS (ASM), GBDK (C), ZGB (C), GB Studio, ...

### General Criteria

15. Teams are allowed (prizes will be split).
16. ROM Hacks are not allowed.
17. NSFW content is not allowed. Please keep it safe for work and avoid anything overly edgy or distasteful.
18. The entry must be submitted on the itch.io jam page.
19. Your work must be new and original. You cannot enter with something you were already working on before the start of the jam. The majority of work should be done during the jam. If in doubt, please ask us before proceeding.

    Examples of things that are **allowed:**

    - A brand new game using your own (or any) existing game engine.
    - A remake of your (or someone else's) game for a different platform, but with new code and assets.
    - A brand new game you barely started (e.g. you only made a title screen or a mockup of the gameplay).
    - Using templates, tutorial code as starting point (when they're license allow to do so, e.g. [gb-boilerplate](https://github.com/ISSOtm/gb-boilerplate))
    - Pre-made and AI generated assets for art and music (and covers of other music) are allowed if the licensing permits. Be sure to make note of what is being reused or re-interpreted and under what terms. There is a specific field in the submission form to allow you to disclose this. Use of pre-made and AI generated assets and content (vs wholly original and human created) may be factored in when judges assess points for relevant criteria (e.g. graphics, audio).

    Examples of things that are **not allowed:**

    - A project you've been working on for months.
    - An update/patch for a game you already released publicly.

20. The submission must be available for free for the public (and not only the judges). Submission will be published and kept online for free on the competition website, while you are free to keep working on it (and eventually charge for it/make commercial usage).
21. It is allowed to submit an entry to multiple events.
22. It is permitted to use the 'gbcompo25 logo' graphics to identify the entry as a submission to this competition (e.g. as an intro splash screen). Minor modifications and alterations (color palette, size, smaller resolution) to fit the medium are permitted.
23. Before submitting, make sure you've read the rules. Our resources are limited - low effort, non-compliant or spammy submissions may lead to exclusion from this event and future ones.

## Judging

A team of (~10) judges has been selected before the start of the competition.

Every judge will be asked to compile an unranked shortlist of their favourite entries. Once the shortlists are ready from all the judges, the top 15 most selected entries will be evaluated by the judges on the following criteria:

1. **Gameplay** - How entertaining are the gameplay mechanics? How fun is the game?
2. **Technical** - How innovative is it from a technical perspective? Does it push the hardware to the limit?
3. **Originality** - How refreshing is it from a non-technical perspective? Does it have a unique design / mechanics?
4. **Graphics** - How impressive are the art, animation and visual effects?
5. **Audio** - How good are the music and sound effects?

Applicability may vary depending on the category of the entry (e.g. a "Music" ROM may be evaluated only on 5)-

The final leaderboard is computed by averaging the "Overall" score.

::: warning
Judges are volunteers. Those details can change without prior notice, as the judging process is long and involves hundreds of entries. Detailed criteria leaderboards will be provided only for entries in the final shortlist.

:::

## Acknowledgements

Special thanks to everyone who helped organising and running this event:

**Partners**: gbdev.io, LaroldsJubilantJunkyard

**Organisers**: avivace, bbbbbr

**Judges**: Maithonis, DUO, Fei, Veund, David Couzelis, Sanqui, j0nas, RetroBreak, Ferrante


## Results

### Games

|   # | Name                       |   Overall |   Gameplay |   Technical |   Originality |   Graphics |   Audio |
|----:|:---------------------------|----------:|-----------:|------------:|--------------:|-----------:|--------:|
|   1 | **OrmBlok** †              |      3.84 |       4.00 |        3.67 |          4.22 |       3.22 |    4.11 |
|   2 | **Fortune & Glory** † §    |      3.82 |       4.22 |        3.22 |          3.22 |       4.78 |    3.67 |
|   3 | **Potion Panic** † §       |      3.49 |       3.33 |        3.89 |          3.33 |       3.78 |    3.11 |
|   4 | **DropBlox!** †            |      3.47 |       3.89 |        3.67 |          4.11 |       2.67 |    3.00 |
|   5 | **Game of Life Kuzushi** † |      3.42 |       3.67 |        3.78 |          4.11 |       2.44 |    3.11 |
|   6 | Sheep Split                |      3.40 |       3.67 |        2.56 |          3.78 |       3.56 |    3.44 |
|   7 | PocketWare                 |      3.36 |       3.44 |        3.33 |          2.78 |       3.67 |    3.56 |
|   8 | Cosmo Command              |      3.33 |       3.00 |        2.67 |          3.11 |       4.22 |    3.67 |
|   9 | CLEAVING CALIBER EX        |      3.22 |       3.33 |        3.11 |          3.56 |       4.11 |    2.00 |
|  10 | FUN VIDEO STORE            |      3.16 |       2.44 |        2.44 |          3.56 |       4.00 |    3.33 |
|  10 | The Sweeping Wizard        |      3.16 |       3.56 |        3.11 |          3.56 |       2.56 |    3.00 |
|  10 | Abyss                      |      3.16 |       3.00 |        2.22 |          3.44 |       4.11 |    3.00 |
|  14 | Ningami 2                  |      3.11 |       3.78 |        3.00 |          3.22 |       2.67 |    2.89 |
|  15 | Mount Crymore              |      3.00 |       3.89 |        2.67 |          2.22 |       2.89 |    3.33 |
|  16 | Peasant Republic           |      2.98 |       2.89 |        2.67 |          2.89 |       3.22 |    3.22 |
|  17 | PuzzleBoy: Logic Quest     |      2.13 |       3.00 |        3.00 |          1.44 |       2.00 |    1.22 |

Entries marked with **†** are awarded the "Best Games" prizes and the ones with **§** get the "Best Open Source Games".

### Tools

1. **Tomato-do** †
2. GBC Pip-Boy
3. GB Tile Editor
4. Still Got the Blues

### Music

1. **Neon Nova** †
2. **DMG Keygen** †

### Demoscene

No prize was awarded in the demoscene category due to lack of qualifying entries.

## Past editions

- [GB Competition 2021](https://itch.io/jam/gbcompo21)
- [GB Competition 2023](https://itch.io/jam/gbcompo23)

---

## File: website/meetings.md

---
sidebar: false
---

# Meetings minutes

- [March 10, 2024](./meetings/2024-03-10-sc2.html)
- [November 4th, 2023](./meetings/2023-11-04-sc.html)

---

## File: website/newsletter.md

---
sidebar: false
editLink: false
---

# Newsletter

- [gbdev newsletter Issue #1 - 2021](/newsletter/1)
- [State of GBDev - March 2021](https://eldred.fr/blog/gbdev-status)
- [gbdev updates - November 2020](https://avivace.com/posts/gbdev-updates-nov2020/)

---

## File: website/privacypolicy.md

---
sidebar: false
editLink: false
---

# Privacy Policy

We use a self hosted instance of [Matomo](https://en.wikipedia.org/wiki/Matomo_(software)), a free and open source web analytics application to track online visits to our websites (`*.gbdev.io`, `*.gbadev.net`), in order to better understand which audience we serve and what content is consulted.

### How

- Visitors' IP addresses are **anonymized**. The last byte is masked and fully qualified addressess **never** reach our analytics software.
- User IDs are replaced by **pseudonyms** to avoid directly storing and displaying personally identifiable information.
- We ignore any existing tracking cookie and our tracking **DOES NOT use any cookie**. (See also: [What are cookies?](https://www.cookiesandyou.com/))
- We honor your browser "I do not want to be tracked" preference.
- Data is aggregated and reports are compiled. Raw logs are regularly cleaned up.

You can opt out by clicking [here](https://stats.gbdev.io/index.php?module=CoreAdminHome&action=optOut&language=en).

---

## File: website/to_c_or_not_to_c.md

---
sidebar: false
head:
  - - meta
    - http-equiv: refresh
      content: 10; url=https://gbdev.io/guides/tools.html
  - - link
    - rel: canonical
      href: https://gbdev.io/guides/tools.html
---

# To C or not to C

This guide has been moved to
"[Choosing tools for Game Boy development](guides/tools.html)".

Please update your bookmarks and ask other website operators
to update inbound links.

In 10 seconds, you'll be redirected.

---

## File: website/guides/asmstyle.md

---
head:
  - - meta
    - name: og:title
      content: GB ASM style guide
  - - meta
    - name: og:description
      content: This style guide aims to formalize a style that most Game Boy ASM programmers agree on, and provide a good baseline for new programmers just starting in this field.
---

# Game Boy ASM style guide

Written by [ISSOtm](https://github.com/ISSOtm/)

---

This style guide aims to formalize a style that most Game Boy ASM programmers agree on, and provide a good baseline for new programmers just starting in this field. (If that's you, welcome! :D)

To quote the [Linux kernel style guide](https://github.com/torvalds/linux/blob/master/Documentation/process/coding-style.rst):

> Coding style is very personal, and I won't **force** my views on anybody, but this is what goes for anything that I have to be able to maintain, and I'd prefer it for most other things too. Please at least consider the points made here.

Many people follow alternate style guides, and that's fine; but if you're starting to code in ASM, a clean style goes a long way to keep your code organized. Again: you don't have to do everything listed here, but please at least consider the reasons behind each bullet point.

Oh, by the way, you're free to [contribute to this document](https://github.com/gbdev/gbdev.github.io) and/or [chat with us about it](https://gbdev.io/chat)!

## Naming

[RGBASM accepts a lot of symbol names](https://rgbds.gbdev.io/docs/v0.4.2/rgbasm.5#SYMBOLS):

> Symbol names can contain letters, numbers, underscores ‘_’, hashes ‘#’ and at signs ‘@’. However, they must begin with either a letter, or an underscore. 

However, naming conventions make code easier to read, since they help convey the different semantics between each symbol's name.

- Labels use PascalCase: `DrawNPCs`, `GetOffsetFromCamera`.
- Labels in RAM (VRAM, SRAM, WRAM, HRAM; you shouldn't be using Echo RAM or OAM) use the same convention but are prefixed with the initial of the RAM they're in, in lowercase: `wCameraOffsetBuffer`, `hVBlankFlag`, `vTilesetTiles`, `sSaveFileChecksum`. *Rationale: to know in which memory type the label is; this is important because VRAM and SRAM have special access precautions and HRAM can (should (must)) be accessed using the `ldh` instruction.*
- Local labels use camelCase, regardless of memory type: `.waitVRAM`, `wPlayer.xCoord`.
- Macro names use snake_case: `wait_vram`, `end_struct`.
- Constants use CAPS_SNAKE: `NB_NPCS`, `OVERWORLD_STATE_LOAD_MAP`.

  Exception: constants that are used like labels should follow the label naming conventions. For example, see [hardware.inc](https://github.com/gbdev/hardware.inc/blob/master/hardware.inc)'s `rXXX` constants.

## Best practices

- Avoid hardcoding things. This means:
  * No magic numbers. `ld a, CURSOR_SPEED` is much more obvious than `ld a, 5`. In addition, if you ever change your mind and decide to change the cursor speed, you will only need to do so in one location (`CURSOR_SPEED equ 5` → `CURSOR_SPEED equ 4`) instead of at every location you're using it, potentially missing some.
  * Unless **absolutely necessary**, don't [force a `SECTION`'s bank](https://rgbds.gbdev.io/docs/v0.4.2/rgbasm.5#BANK) or address. This puts the burden of managing ROM space on you, instead of offloading the job to RGBLINK, which performs very well in typical cases. Exceptions:
    - Your ROM's entry point [must be at $0100](https://gbdev.io/pandocs/#_0100-0103-entry-point), however the jump does not have to be to $0150 ([example](https://github.com/GreenAndEievui/vuibui-engine/blob/206fd814e67da2cebbeca7d011a5537fef22a29c/src/main.asm#L6)).
    - [`rst` vectors and interrupt handlers](https://gbdev.io/pandocs/#jump-vectors-in-first-rom-bank) obviously need to be at the corresponding locations.
    - [RGBDS presently does not allow forcing different sections to be in the same bank](https://github.com/gbdev/rgbds/issues/244). If you need to do so, the ideal fix is to merge the two sections together (either by moving the code, or using [`SECTION FRAGMENT`](https://rgbds.gbdev.io/docs/v0.4.2/rgbasm.5#Section_Fragments)), but if that option is unavailable, the only alternative is to explicitly declare them with the same `BANK[]` attribute. (In which case it's advisable to add an `assert BANK("Section A") == BANK("Section B")` line.)

    If you need some alignment, prefer [`ALIGN[]`](https://rgbds.gbdev.io/docs/v0.4.2/rgbasm.5#ALIGN) to forcing the address. A typical example is [OAM DMA](https://gbdev.io/pandocs/#lcd-oam-dma-transfers); for that, prefer `SECTION "Shadow OAM", WRAM0,ALIGN[8]` over e.g. `SECTION "Shadow OAM", WRAM0[$C000]`.

- Allocate space for your variables using [labels](https://rgbds.gbdev.io/docs/v0.4.2/rgbasm.5#SYMBOLS) + [`ds` & co](https://rgbds.gbdev.io/docs/v0.4.2/rgbasm.5#Declaring_variables_in_a_RAM_section) instead of [`equ`](https://rgbds.gbdev.io/docs/v0.4.2/rgbasm.5#EQU). This has several benefits:
  * Removing, adding, or changing the size of a variable that isn't the last one doesn't require updating every variable after it.
  * The size of each variable is obvious (`ds 4`) instead of having to be calculated from the addresses.
  * `equ` allocation is equivalent to hardcoding section addresses (see above), whereas labels are placed automatically by RGBLINK.
  * Labels support [`BANK()`](https://rgbds.gbdev.io/docs/v0.4.2/rgbasm.5#Other_functions) and many cool other features!
  * Labels are output in [`map` and `sym`](https://rgbds.gbdev.io/docs/v0.4.2/rgblink.1#m) files.

- If a file gets too big, you should split it. Files too large are harder to read and navigate. However, the splitting should stay coherent and consistent; having to jump around files constantly is equally as hard to read and navigate.

- [Unless you're making a 32k ROM](https://gbdev.io/pandocs/#no-mbc), put things in [`ROMX`](https://rgbds.gbdev.io/docs/v0.4.2/rgbasm.5#ROMX) by default. `ROM0` space is precious, and can deplete quickly; and when you run out, it's difficult to move things to ROMX.

  However, if you have code in ROM bank A refer to code or data in ROM bank B, then either should probably be moved to ROM0, or both be placed in the same bank (options for that are mentioned further above). [`farcall`](https://github.com/pret/pokecrystal/blob/35219230960f0dc85c0cb6a5723877b247609e46/macros/rst.asm#L1-L5) is a good way to make your code really [spaghetti](https://en.wikipedia.org/wiki/Spaghetti_code).
  
- Don't clear RAM at init! Good debugging emulators will warn you when you're reading uninitialized RAM ([BGB](https://bgb.bircd.org) has one in the option's Exceptions tab, for example), which will let you know that you forgot to initialize a variable. Clearing RAM does not fix most of these bugs, but silences the helpful warnings.

  Also, a lot of the time, variables need to get initialized to values other than 0, so clearing RAM is actually counter-productive in these cases.

## Recommendations

The difference between these and the "best practices" above is that these are more subjective, but they're still worth talking about here.

- Historically, RGBDS has required label definitions to begin at "column 1" (i.e. no whitespace before them on their line). However, later versions (with full support added in 0.5.0) allow [indenting labels](https://github.com/pinobatch/libbet/blob/cabe48bc4042338b9975cb32c2dbd0ee6640f31e/src/main.z80#L206-L231), for example to make loops stand out like in higher-level languages. However, [a lot of people don't do this](https://github.com/gbdev/DeadCScroll/blob/9834372eb0d56e8b9a8cdcaae4b8aecb6d402266/DeadCScroll.asm#L410-L422), so it's up to you.

- Please use the `.asm` (or `.s`) file extensions, not `.z80`. The GB CPU isn't a Z80, so syntax highlighters get it *mostly* right, but not quite. And it helps spreading the false idea that the GB CPU is a Z80. :,(

- Compressing data is useful for several reasons; however, it's not necessary in a lot of cases nowadays, so you may want to only look at it after more high-priority aspects.

- Avoid abusing macros. Macros tend to make code opaque and hard to read for people trying to help you, in addition to having side effects and sometimes leading to very inefficient code.

- Never let the hardware draw a corrupted frame even if it's just one frame. If it's noticeable by squinting a bit, it must go.

- [Makefiles are bae](https://www.gnu.org/software/make/manual/html_node/); they speed up build time by not re-processing what hasn't changed, and they can automate a lot of tedium. Writing a good Makefile can be quite daunting, but [gb-boilerplate](https://github.com/ISSOtm/gb-boilerplate) and [gb-starter-kit](https://github.com/ISSOtm/gb-starter-kit) can help you get started faster.

---

## File: website/guides/deadcscroll.md

---
head:
  - - meta
    - name: og:title
      content: Dead C Scroll tutorial
  - - meta
    - name: og:description
      content: An assembly tutorial for Game Boy showing how the scroll registers can be exploited to create some nice and interesting effects.
---

# Dead C Scroll

Written by Bob.

---

An assembly tutorial for Game Boy showing how the scroll registers can be exploited to create some nice and interesting effects.

Files related to this tutorial can be found [here](https://github.com/gbdev/gbdev.github.io/tree/dev/website/.vuepress/public/deadcscroll).

## Introducing the registers

### SCY ($FF42)/SCX ($FF43)

The `SCY`/`SCX` registers have a simple purpose: specify the coordinate of the screen's top-left pixel (or view, if you prefer) somewhere on the 256x256 pixel background map. This is really handy for certain kinds of games like platformers or top-down racing games (though there are LOTS of other kinds of games that benefit from this) where the view is the 'camera' and its position is set once per frame.

When you don't require scrolling, and when your cart boots, `SCY`/`SCX` is typically set to 0,0. When a screen is displayed, it appears normally even though you only set the values once. This is because as the screen draws, the PPU automatically adds the value in `LY` ($FF44) to the value in `SCY` in order to know what row of pixels to draw.

```:no-line-numbers
SCY value (set once)
│
│      screen
│     ┌───────┐
└─$00 │Line 0 │  VRAM row $00 ($00+$00) is displayed
      │Line 1 │  VRAM row $01 ($00+$01) is displayed
      │Line 2 │  VRAM row $02 ($00+$02) is displayed
      │Line 3 │  VRAM row $03 ($00+$03) is displayed
      │Line 4 │  VRAM row $04 ($00+$04) is displayed
      │...    │  ...
      │       │
      └───────┘
```
If `SCY` = $20 (for example):
```:no-line-numbers
SCY value (set once)
│
│      screen
│     ┌───────┐
└─$20 │Line 0 │  VRAM row $20 ($20+$00) is displayed
      │Line 1 │  VRAM row $21 ($20+$01) is displayed
      │Line 2 │  VRAM row $22 ($20+$02) is displayed
      │Line 3 │  VRAM row $23 ($20+$03) is displayed
      │Line 4 │  VRAM row $24 ($20+$04) is displayed
      │...    │  ...
      │       │
      └───────┘
```

You can take advantage of how the PPU renders the screen by setting these registers *as the screen draws*. If you do this, you can create some interesting 'raster' effects that are presented here.

As an example, let's say you wanted to triple line 0 and show it for line 0, line 1, and line 2, and then continue with line 3. You would write to the `SCY` register like so:
```:no-line-numbers
SCY value (set once per line)
│
│      screen
│     ┌───────┐
├─$00 │Line 0 │  VRAM row $00 ($00+$00) is displayed
├─$FF │Line 1 │  VRAM row $00 ($FF+$01) is displayed
├─$FE │Line 2 │  VRAM row $00 ($FE+$02) is displayed
├─$00 │Line 3 │  VRAM row $03 ($00+$03) is displayed
├─$00 │Line 4 │  VRAM row $04 ($00+$04) is displayed
├─... │...    │  ...
      │       │
      └───────┘
```

When setting values for `SCY`, you need to remember that `LY` always (and automatically) increments, so you have to account for that in your new `SCY` value. You can write anything to `SCX`; that's not affected by the hardware so you don't have to adjust the value like you need to for `SCY`.

> Note: The scroll registers only affect background rendering. They do not change how objects are displayed.

## Implementation

There are three main states that drive the display on the Game Boy: the Horizontal Blank (HBlank), the Vertical Blank (VBlank), and drawing. The HBlank starts when a line of pixels is completely drawn. There is an opportunity to do some work\* before the next line of pixels starts drawing, and there is one HBlank for every line, all the way down the screen.

> \**The exact amount of time you have depends on several things; most notably how many objects are being drawn on that line. The [PanDocs](https://gbdev.io/pandocs/#pixel-fifo) has a detailed explanation of the timing. (Indeed, read that entire document because it's great!)*

When all of the lines are completely drawn, the VBlank starts. This interval is always 10 lines high so there is much more time to do some work compared to the HBlank. The VBlank is only secondary to this system though; the focus is the HBlank since we want to change the screen as it draws. The problem that needs to be solved is reliably knowing what value to set for a specific line.

As previously mentioned, there is a small amount of time that HBlanks give you to do work. This means that the handler has to be as fast as possible. On a limited system like the Game Boy, that usually equates to judicious use of table lookups and buffers.

There are two key elements to make this system very stable and very fast:
1. A double-buffering system that holds the data that feeds the HBlank handler
2. How the buffers are arranged

### Double Buffering

The idea of the double-buffer is that while one buffer is being used by the hardware to draw the screen, you modify (fill) values in the other. When the screen is done drawing, you switch buffers so the one you were just modifying is being used for drawing and you start modifying the other.

While the Draw Buffer (A) is used to render the screen, you change values in the Fill Buffer (B).
```:no-line-numbers
┌───────┐   ┌───────┐
│Draw   │   │Fill   │
│Buffer │   │Buffer │
│       │   │       │
│       │   │       │
│       │   │       │
│       │   │       │
│A      │   │B      │
└───────┘   └───────┘
```

When the screen is done being drawn (and you know this because the VBlank interrupt would have triggered or the value in `LY` changed to 144), you switch the buffers.
```:no-line-numbers
┌───────┐   ┌───────┐
│Fill   │   │Draw   │
│Buffer │   │Buffer │
│       │   │       │
│       │   │       │
│       │   │       │
│       │   │       │
│A      │   │B      │
└───────┘   └───────┘
```

Here, "switch buffers" means to switch the *purpose* of each buffer. It doesn't mean to copy buffers. Remember, we need this to be as fast as possible so to change buffers, you simply change pointers:
```:no-line-numbers
Draw-->┌───────┐   Fill-->┌───────┐
Ptr    │Buffer │   Ptr    │Buffer │
       │A      │          │B      │
       │       │          │       │
       │       │          │       │
       │       │          │       │
       │       │          │       │
       │       │          │       │
       └───────┘          └───────┘
```
Becomes:
```:no-line-numbers
Fill-->┌───────┐   Draw-->┌───────┐
Ptr    │Buffer │   Ptr    │Buffer │
       │A      │          │B      │
       │       │          │       │
       │       │          │       │
       │       │          │       │
       │       │          │       │
       │       │          │       │
       └───────┘          └───────┘
```

#### Buffer Size

The size of each buffer (indeed, any buffer) depends on two things:
- how many elements are needed
- how much data is needed per element

We know that the buffers exist to support the HBlank handler, so the number of elements in the buffer are however many times the HBlank can trigger. We said earlier that the HBlank starts at the end of every screen line, so the number of elements is at least that many. However, remember *when* the HBlank starts: at the *end* of every line. What do we do if we need to change the 0th line (before *any* line has started drawing)? Well, we need to change that value *before* line 0 starts, which means it has to be done in the VBlank. And **that** means we need one more element. In short, we need the height of the screen plus one (144+1=145) elements in each buffer.

This tutorial is only concerned with the scroll registers, so it only needs to store 2 values per line: one for `SCY` and one for `SCX`. (You can store more data per line, of course, but this tutorial doesn't require it.)

In summary: each buffer is 145 2-byte elements (290 bytes), and we need two of them, so the total buffer memory size is 580 bytes.

#### Location in Memory

Assume for a moment that you put the buffers physically next to each other in memory. For example, Buffer A is at `$C000` and Buffer B is at `$C122` (the buffer size is 290 bytes). We said earlier that in order to swap buffers, we just swap pointers, so the code that does that might look like this:

```asm
; assume the pointers are next to each other in memory
wDrawBuffer: DS 2 ; buffer currently being drawn
wFillBuffer: DS 2 ; buffer currently being modified

; swap the contents of each pointer (28 cycles)
ld  hl,wDrawBuffer
ld  a,[hl+]
ld  b,[hl]
ld  c,a     ; bc = contents of wDrawBuffer
inc hl
ld  a,[hl+] ; a = LOW(contents of wFillBuffer)
ld  d,[hl]  ; d = HIGH(contents of wFillBuffer)
ld  hl,wDrawBuffer
ld  [hl+],a
ld  [hl],d
inc hl
ld  a,c
ld  [hl+],a
ld  [hl],b
```
To use a pointer, that code looks like this:
```asm
; use a pointer (8 cycles)
ld  hl,wFillBuffer
ld  a,[hl+]
ld  h,[hl]
ld  l,a  ; hl = contents of wFillBuffer ($C000 or $C122)
```

You could certainly implement the system like this, but there is a way to gain some efficiency when swapping buffers and even with the actual pointers themselves.

Consider this: other than the memory locations, the buffers are identical. Since we're only really concerned with pointers, *where* the buffers reside in memory doesn't really matter. This can be exploited (and optimized!)

We can keep Buffer A at `$C000`. The buffer size is `$122` bytes, but instead of putting Buffer B at `$C122`, what if we put it at `$C200`? This would make the pointer values `$C000` and `$C200`. Literally a 1-bit difference. This, too, can be exploited! Both pointers end in `$00` so we don't need to store those, which saves 2 bytes. This leaves us with two 1-byte 'pointers': `$C0` and `$C2`.

To swap the pointers, literally just one bit has to be toggled:
```asm
; swap the contents of each 'pointer' (11 cycles)
ldh a,[hFillBuffer]
ldh [hDrawBuffer],a
xor $02
ldh [hFillBuffer],a
```
And to use a pointer, we only need to do this:
```asm
; use a 'pointer' (6 cycles)
ldh a,[hFillBuffer]
ld  h,a
ld  l,0  ; hl = contents of hFillBuffer ($C000 or $C200)
```

You'll notice that the name of the pointers have changed. This is because they were moved into HRAM. (Also notice that they don't have to be next to each other in memory.) They were moved to HRAM for a couple of reasons: it allows an optimization in the swapping code (11 cycles vs 28), and it makes the use code slightly faster. There are only 2 bytes used now so that is a better candidate for moving to HRAM than 4 bytes.

### VBlank

In this system, code in the VBlank is responsible for two things:
- swapping the pointers
- setting the data for line 0

We've already seen what swapping the pointers looks like, but how is the data set for line 0? We need to emulate an HBlank handler running for "line -1" by getting the start of the new draw buffer and setting the scroll registers with the first data pair:

```asm
ldh a,[hDrawBuffer]
ld  h,a
ld  l,0

; set the scroll registers
ld  a,[hl+]
ldh [rSCY],a
ld  a,[hl+]
ldh [rSCX],a
```
It's convenient that the scroll register addresses are next to each other. The data in the buffer is in the same order so as you can see in the code fragment above, this makes writing simple.

### HBlank Handler

In an HBlank handler, **every cycle counts**! So don't do any work in there unless it's absolutely necessary. This is a good target for hyper-optimizations -- especially if you are changing VRAM (like palettes) -- so one should design around that optimization.

```asm
HBlankHandler::
  push  af
  push  hl

  ; obtain the pointer to the data pair
  ldh a,[rLY]
  inc a
  add a,a ; double the offset since each line uses 2 bytes
  ld  l,a
  ldh a,[hDrawBuffer]
  adc 0
  ld  h,a  ; hl now points to somewhere in the draw buffer

  ; set the scroll registers
  ld  a,[hl+]
  ldh [rSCY],a
  ld  a,[hl+]
  ldh [rSCX],a

  pop hl
  pop af
  reti
```

Notice that we can take advantage of the fact that there is only 2 bytes per line. We can use `LY` directly and quickly turn it into pointer. (Thanks to rondnelson99 for pointing this out!)

### Use the fill buffer

And there you have it. An automatic and stable way to take advantage of the HBlank to do whatever your imagination wants to do!

All you need to do is set the fill buffer while the draw buffer is being displayed (you have an entire frame's worth of time to do this) and the system does the rest!

## Effects

### X (Horizontal) Sine

This effect uses a sine table to shift each line in a pleasant way. There are 3 states to this effect:
- The image is stable and a progression line moves up the screen starting each line on its way
- The table cycles a few times
- The image stability is restored with the progression line moving up the screen

The values in the table can dramatically change the effect. For example, if the sine cycle was short enough, you could simulate a smoke effect (for example). Try it out!

Also, you could create a 'glitch' effect during a cut-scene, perhaps in a sci-fi game to simulate a slightly dirty transmission.



### Y (Vertical) Sine

This effect is structured very similar to X Sine, in that there is a table of sine values driven by 3 states. The only difference is that `SCY` is changed instead of `SCX`.

This is a really good way to simulate water reflections.



### X and Y Sine

This is simply a combination of the X Sine and Y Sine effects so you can see how different it looks compared to just the X or Y changing.

Instead of a full-screen image like this tutorial uses, imagine if you had a repeating image in VRAM (bigger than the screen) that looked like water ripples. This would move just like water!



### Smear On

This is like a flood fill effect used as an appearance transition. It's quite simple in that it repeats the lines to achieve the 'smear' effect and is perhaps more interesting than a fade in.

The specific image used in the tutorial is light along the bottom so it looks better if the screen was already light before the effect starts. You would change this to suit your image.



### Smear Off

This is a disappearance transition and the reverse of Smear On. Due to the specific image that was used (i.e. it is light along the bottom), it looks better in this tutorial to have the effect reveal a light screen instead of dark. Again, you would change this to suit your image.



### Roll On

This effect simulates an image unrolling onto the screen. This might be useful for fantasy RPGs to transition to a map screen or perhaps a message written on a scroll. The image unrolls over a dark screen because the top of the image is mostly dark so it looks better to keep it dark than the contrast of using a light screen.



### Roll Off

This effect simulates an image rolling off screen. This might be useful for fantasy RPGs to transition away from a map or scroll screen. This reveals a dark screen because the first thing you see in the roll is dark (because that's what's in VRAM below the screen). Keeping it dark made the transition more seamless.



The roll effects look complicated but the implementation is probably one of the simpler ones. The key to make this look good is the values in the table. The roll size is 32 pixels, but you can change this to whatever size you want, provided the table values support it. This [SpecBas demo](https://www.youtube.com/watch?v=j04TKI9WKfo) was used as a reference to obtain those values.

## How to build

A GNU makefile is included. You will have to tailor it for your development environment but it builds cleanly with [RGBDS](https://github.com/gbdev/rgbds) 0.4.2. The only dependency is [`hardware.inc`](https://github.com/gbdev/hardware.inc). All of the effects are shown [here](https://github.com/BlitterObjectBob/ScrollexY#effects) so you don't have to build first to see them.

## Notes about the code

To reduce dependencies, everything is in one .asm file. It's structured in a logical way and there are comments where applicable.

The effects are called "parts" by the code and each part has an `Init` and `Process` routine. The sequence is controlled by a table of `Init` pointers and driven by the `ProcessPartTransition` routine. Each `Init` is responsible for setting up the data for the effect (part) and to set the `Process` function pointer via the `SetProcessFunc` macro. When the effect is done, the `Process` routine calls the `SetChangePartFlag` to tell the tutorial driver to move to the next part.

There are non-effect parts present to get the effect sequence looking good when the parts are played one after the other. These are "delay" parts of various flavors:
- `ShowDelay`: this shows the screen normally for a few seconds
- `LightDelay`: this shows a light-colored blank screen for a few seconds
- `DarkDelay`: this shows a dark-colored blank screen for a few seconds

The `Delay` parts share code because they're only present to make the ROM look nice, but the effects parts were developed in a way to be isolated from one another. This was done to make extraction easier. Because of this, you will see similar code present across several parts, for example, the various Sine effects.

One quirk you might notice when looking at VRAM is that the tile map is placed at 0,4 instead of 0,0. This was done to get the roll/unroll to handle the top of the screen correctly. The effects look best when they smoothly (dis)appear off-screen and if the image was placed at 0,0, the code to handle that would be distracting to how to implement the core of the effect.

Another topic worth mentioning is the row of light tiles that are under the image in VRAM. This was necessary to allow `LightDelay` to exist. Those light tiles don't *have* to be right under the image, that's just where it was placed for this tutorial. It could be moved well out of the way so it doesn't affect the effects that show that part of VRAM (Y Sine, Roll On, Roll Off).

If you run the ROM in [BGB](https://bgb.bircd.org/) and have the Debug Messages window open, you will see the various parts announce themselves when they are initialized.

## Exercises for the reader

You can do more things than just change the scroll registers. For example, you can change the palette. Can you do this to make the roll/unroll effect look better? Here's an [example of scroll register and palette changes](https://www.youtube.com/watch?v=_-GTCao5cxs).

This [appearance effect](https://www.youtube.com/watch?v=leTk0uRnE_g&t=91s) from Sword of Sodan (Amiga) is really cool! (And you might recognize one of the opening effects if you scrub to the beginning.)

Another raster effect you could do is a 'twist' like the one in the [Wired demo](https://www.youtube.com/watch?v=WlMl8XKCb1Y&t=63s).

You can use this system to make a racing game similar to [F-1 World Grand Prix II](https://www.youtube.com/watch?v=yvbQD2pbJes) or [Wacky Races](https://www.youtube.com/watch?v=1kXiU_odMMM&t=110s). How might you achieve this?

## PRs are welcome!

Other effects can be done, such as flipping the entire image about the X axis to look like its tumbling. What other effects can you create?

## Acknowledgements

Thanks go to Baŝto for use of the [Dead Boy](https://opengameart.org/content/dead-boy) image and [ISSOtm](https://github.com/ISSOtm) for peer review!

## License

This was released for educational purposes and so is placed in the Public Domain. See [LICENSE](https://github.com/gbdev/gbdev.github.io/blob/dev/list/.vuepress/public/deadcscroll/LICENSE) for more details.

---

## File: website/guides/dma_hijacking.md

---
head:
  - - meta
    - name: og:title
      content: DMA Hijacking on Game Boy
  - - meta
    - name: og:description
      content: A technique that allows you to run custom code in most GB/SGB/CGB games, provided you have an ACE exploit.
---

# DMA Hijacking

Written by [ISSOtm](https://github.com/ISSOtm).

::: tip TARGET AUDIENCE

Unlike most resources here, this guide is not very useful to developers or even ROM hackers, but rather to glitch-hunters and exploit developers.

:::

## What is it?

*OAM DMA hijacking* is a simple technique that allows you to run custom code in most GB/SGB/CGB games, provided you have an ACE exploit.

One would be quick to point out that if you have an ACE exploit, you can already execute custom code.
So then, what is the point?
It's that code ran through DMA Hijacking will be run *on every game frame* (for most games, at least).

## How is it done?

If you are familiar enough with [OAM](https://gbdev.io/pandocs/OAM), you may know about a feature called *OAM DMA*.

[OAM DMA](https://gbdev.io/pandocs/OAM_DMA_Transfer) is a convenient feature that allows quickly updating the on-screen ["objects"](https://gbdev.io/pandocs/Rendering#objects) (often known as "sprites") quickly—which is especially useful since it typically needs to occur on every frame.
However, using OAM DMA requires a small routine to be copied to HRAM and then run from there.

Interestingly, most games only copy the routine when starting up, and then execute it on every subsequent frame.
But, *if we modified that routine while the game is running*, then the game will happily run the customized routine!

### Patching the code

Here is the standard routine, given by Nintendo in the GB programming manual (using [RGBASM syntax](https://rgbds.gbdev.io/docs/rgbasm.5) and a symbol from [`hardware.inc`](https://github.com/gbdev/hardware.inc)):

```asm
    ld a, HIGH(OAMBuffer)
    ldh [rDMA], a  ; $FF46
    ld a, 40
DMALoop:
    dec a
    jr nz, DMALoop
    ret
```

The simplest way to get custom code (let's call it `DMAHook`) executed would be to overwrite the first few bytes with a jump to `DMAHook`:

```asm{1}
    jp DMAHook
    db $46    ; Leftover operand byte of `ldh [rDMA], a`
    ld a, 40  ; None of this is executed
DMALoop:
    dec a
    jr nz, DMALoop
    ret
```

Now, overwriting the routine like this works for our purposes, but comes with a large drawback: the routine isn't doing what it is intended to anymore, and so the game's objects won't update (unless you manually copied OAM, but beware of [the OAM corruption bug](https://gbdev.io/pandocs/OAM_Corruption_Bug)).
Further, it's not possible to write to `rDMA` from `DMAHook`, as the write and subsequent wait loop **must** be executed from HRAM.

But, there is a solution.

```asm{1-2}
    call DMAHook
    ldh [c], a  ; A write to `rDMA`, set up by DMAHook
    ld a, 40
DMALoop:
    dec a
    jr nz, DMALoop
    ret
```

Provided that `DMAHook` returns with properly set registers, this allows writing to `rDMA` in the single HRAM byte left by the `call` instruction.
Here is a pattern for DMAHook :

```asm
DMAHook:
    ;;  Custom code, do whatever you want, it's VBlank time!
    ; ...
    ld c, LOW(rDMA)  ; $46
    ld a, HIGH(OAMBuffer)
    ret
```

`DMAHook` can live anywhere in memory, but typically it will be in WRAM.
It will be executed in the context of the VBlank interrupt, so for most games interrupts will be disabled, etc.

## With Cartswap

DMA Hijacking is also useful when combined with [cartswap](https://gist.github.com/ISSOtm/3008fd73ec66cb56f1caecfcc8b6fb6f) (swapping carts without shutting the console down, concept found by furrtek, developed by Cryo and me on the GCL forums), because it allows "transporting" ACE to other games.

General procedure:

1. Acquire ACE in the "source" game
1. Perform cartswap, insert the "victim" game
1. "Pseudo-initialize" the victim
1. Place the modified DMA handler in HRAM
1. Transfer control back to the victim's ROM
1. ????
1. Profit!

Possible applications are checking for a button combo to trigger specific code (for example, credits warp), checking one or multiple memory addresses to detect a certain game state, etc.

Possible "attack vectors", i.e. ways of affecting the victim game, are setting certain memory addresses (like a GameShark), or even better: manipulating the stack.

Here is a video demonstration:


Manipulating the stack with this technique can not crash if the triggering game state is specific enough.
I achieved text pointer manipulation in Pokémon Red this way.
(This is not a ROM hack!)


### Details

This new technique hinges on breaking one of any game's core assumptions: its entry point.
You see, normally, [the console transfers control to the game at address $0100](https://gbdev.io/pandocs/The_Cartridge_Header#0100-0103---entry-point), so any code placed there is designed to initialize all of the game's systems, in particular their memory.

However, since we have control of the CPU, we can jump to any location in the game's ROM, which allows bypassing some of said initialization.
Doing so without any precautions is very likely to go haywire, though—it is important to initialize *enough* that the game runs, but not *too much* that it would end up overwriting the code we are trying to inject.
This is what I call "**pseudo-initialization**".

Another important part is finding some free space to store the hook code in.
The stack area can work surprisingly well for this, as many games appear to over-allocate (e.g. 256 bytes when the typical usage doesn't go beyond 32).

None of this has a silver bullet: the game's init code must be analyzed, and its memory usage carefully scrutinized in order to dig up enough free space for your hook.

## Trivia

DMA hijacking works similarly to the GameShark: that device intercepts accesses to the ROM, and when it detects that the VBlank handler is being run, it "overlays" different instructions that apply the stored codes, and jump back to the actual handler.

And, why yes, it is possible to use DMA hijacking to emulate GameShark codes!
[Here is a proof-of-concept in Pokémon Red](http://gbdev.gg8.se/forums/viewtopic.php?id=430).

## Notes

- I encountered some games that don't transfer OAM unless a specific flag is set; I believe that it is always possible to override this limitation, by setting the flag back in the hook.
- The OAM DMA routine is often placed at $FF80 in commercial games.
- The patched OAM DMA routine with our hook may be modifying registers that the game expects to be preserved.
  This is all dependent on the target game, so no general advice can be given.

  Additionally, if the hook takes too long, it may cause code expecting to run in VBlank to break.
  This might be solved for example by manipulating the stack and injecting an additional return address; here is an example.
  ```asm
      jp DMAHook
  PostDMAHook:
      ldh [c], a
      ld a, 40
  DMALoop:
      dec a
      jr nz, DMALoop
      jp hl
  ```
  ```asm
      pop hl  ; Get original return address
      ld bc, PostHandlerHook  ; Address of code that will be executed once the VBlank handler finishes
      push bc  ; Inject return address for VBlank handler
      ld c, LOW(rDMA)
      ld a, HIGH(OAMBuffer)
      jp PostDMAHook
  ```
  (Since the handler almost certainly performs some `pop`s before returning, you will almost certainly need more complex stack manipulation, but that's the gist of it.)
- Some games have a slightly more clever routine in HRAM, that omits the initial `ld a, HIGH(OAMBuffer)` saving 2 bytes of HRAM.
  ```asm
      ldh [rDMA], a
      ld a, 40
  DMALoop:
      dec a
      jr nz, DMALoop
      ret
  ```
  They can still be patched by overwriting the `ld a, 40` instead, and using e.g. the `b` register for the loop:
  ```asm{1-2,4}
      call DMAHook
      ldh [c], a  ; Write to rDMA
  DMALoop:
      dec b
      jr nz, DMALoop
      ret
  ```
  Then `DMAHook` needs to return with `b` additionally set to 40:
  ```asm{4-5}
  DMAHook:
      ;;  Custom code, do whatever you want, it's VBlank time!
      ; ...
      ld bc, 40 << 8 | LOW(rDMA)  ; 40 in B, $46 in C
      ld a, HIGH(OAMBuffer)
      ret
  ```
  However, if the OAM buffer address passed to the function (in `a`) is not static, `push af` and `pop af` will have to be used instead of `ld a, HIGH(OAMBuffer)`.

---

## File: website/guides/lyc_timing.md

---
head:
  - - meta
    - name: og:title
      content: Timing of LYC STAT Handlers
  - - meta
    - name: og:description
      content: Guide on how to implement raster effects with the rLYC register.
---

# The Timing of LYC STAT Handlers

Written by [Ron Nelson](https://github.com/rondnelson99/) and [ISSOtm](https://eldred.fr)

---

Raster effects are probably the greatest assets that retro game consoles have.
The fact that the PPU generates the image right as it is displayed allows many special effects to be created by modifying the rendering parameters while the image is being drawn.
Here is an example:

<figure>



</figure>

However, unlike some consoles like the SNES, the Game Boy contains no hardware dedicated to raster effects, so the task falls squarely on the CPU.
This causes raster FX code to interact with the rest of the program in complex ways, particularly when it comes to [accessing VRAM](https://gbdev.io/pandocs/Accessing_VRAM_and_OAM).

In this article, we will explore different techniques for handling raster effects, and discuss their pros and cons with the help of some diagrams.

::: warning PRIOR KNOWLEDGE ASSUMED

This article is not a friendly introduction to programming raster effects, and assumes you are already comfortable with Game Boy programming.
To learn more about how to achieve neat raster effects like the above, check out [DeadCScroll](deadcscroll.md) first, which the above GIF is actually from!

Additionally, since the operations discussed here are *extremely* timing-sensitive, discussions will revolve around assembly instructions.
You can learn how to program for the Game Boy in assembly in [GB ASM Tutorial](https://eldred.fr/gb-asm-tutorial).

:::

::: tip TERMINOLOGY

We'll reference a few terms throughout this tutorial; here are brief explanations of them:

- **SoC**: [System-on-a-Chip](https://en.wikipedia.org/wiki/System_on_a_chip), a single chip that includes most (or all!) components of a system. The Game Boy's functionality is almost entirely contained within a single chip, confusingly labelled "DMG-CPU" or similar. (Contrast this with, for example, the SNES, where there is one chip for the CPU, two for the PPU, and many more.)
- **CPU**: Central Processing Unit, the part of the SoC that executes code and configures everything else.
- **PPU**: Pixel Processing Unit, the part of the SoC that is responsible for sending pixels to the LCD and generating them.
- **Rasterization**: the process of turning... something (for example, a collection of textured polygons; or, on the GB, tiles and tilemaps) into an array of pixels. "Raster" is sort of a contraction of that term.
- **Scanline**: a row of pixels; it's called a "scan"-line because the lines get drawn one by one, pixel by pixel, as if the PPU was "scanning" along the screen.
- **Register**: in general, a small piece of memory, usually linked to some hardware component.
- **PPU mode**: The PPU can be in one of four modes at a given time, depending on what it's doing. [Please refer to Pan Docs](https://gbdev.io/pandocs/STAT) to learn what each mode corresponds to  and how they are scheduled—they interact very tightly with raster effects.
- **Interrupt**: an event that gets generated. Typically, this causes a "handler" to be `call`ed, which is a special routine dedicated to reacting to a given interrupt.
- **"Main thread"**: any code that is executed outside of interrupt handlers.

:::

## Introduction

The easiest way to implement raster effects is to use the `LYC` register with the STAT interrupt.

Here is what the Pan Docs have to say about this register's simple function:

> [**FF45 - LYC (LY Compare) (R/W)**](https://gbdev.io/pandocs/Scrolling#ff45---lyc-ly-compare-rw)
>
> The Game Boy permanently compares the value of the LYC and LY registers.
> When both values are identical, the “LYC=LY” flag in the STAT register is set, and (if enabled) a STAT interrupt is requested.

So then, the outline for setting up a raster effect is as follows:

1. Register an interrupt by setting `LYC` to the desired scanline
2. When that scanline begins, the STAT interrupt handler will automatically be called
3. Perform your chosen effect by modifying PPU registers
4. Exit the handler with `reti`

::: tip ALTERNATIVES

There are other ways to perform raster FX, such as busy-waiting in the "main thread", but as this article's title suggests, we won't discuss them here.

A major pro of `LYC`-interrupt-based raster effects is that they can be made self-contained, and thus largely independent of whatever the "main thread" is doing.
This, in turn, simplifies the mental complexity of the code (decoupling), copes better with lag frames, and more.

Many of the points brought forth later, particularly regarding cycle counting, are still relevant with these alternatives, so this is still worth reading!

:::

These four steps sound simple enough on their own, but there are numerous caveats we will discuss.
Strap in!

- Most raster effects are implemented by modifying registers *between* scanlines.
  Thus, you will want to write the register either during Mode 2 (of the same scanline), or Mode 0 (of the previous one)—anything but Mode 3, really.  
  Unfortunately, `LY=LYC` interrupts are requested at the beginning of a scanline, so during the very short Mode 2, leaving too little time to perform but the most basic of effects.
- Writing to the register during HBlank instead implies triggering the interrupt on the scanline *above* the effect, as well as idling for most of the scanline. So, if I wanted to enable sprites on scanline 16, I'd write 15 to `LYC`.
- Mode 3's length is variable, so syncing to HBlank is difficult and time-consuming.
- The interrupt handler's execution may be delayed by a few cycles, which makes it difficult to reliably sync to the PPU.
- If the "main thread" is itself trying to sync with the PPU (typically by polling `STAT` in a loop), our interrupt may throw off its timing.

Sounds good?
Then let's get started!

## Timing

First, let's look at the timing of the rendering itself, [courtesy of the Pan Docs](https://gbdev.io/pandocs/pixel_fifo):



Here are some key points:
- A "dot" is one period of the PPU's 4 MiHz clock, i.e. 0.25 µs.
- A "cycle" is the main unit of time in the CPU, which is equal to 1 µs, or 4 dots. (The Game Boy Color CPU can enter a "double-speed" mode which halves the length of cycles, but not of dots. For the sake of simplicity, we won't consider the differences it involves here.)
- Each scanline takes exactly 456 dots, or 114 cycles.
- Mode 2 also takes a constant amount of time (20 cycles)
- HBlank's length varies wildly, and will often be nearly as long as or longer than the drawing phase.
- HBlank and OAM scan are mostly interchangeable, and long as you're not writing to OAM.
- The worst-case HBlank's length is not a multiple of 4 dots, so we will round down to 21 cycles.

Let's consider a simple STAT handler, which disables OBJs if called at line 128, and enables them otherwise:

@[code asm](lyc_timing/simple_handler.asm)

::: tip

This is not an especially well-written `STAT` handler, but the actual is outside the scope of this tutorial.
If that's what you're looking for, check out [DeadCScroll](deadcscroll) by Blitter Object.
It triggers the STAT interrupt on HBlanks rather than `LYC`, but the fundamentals are the same.

Note that, for simplicity's sake, DeadCScroll does not consider the problems described further below, so be wary of combining that tutorial's STAT handler unmodified with `STAT`-based VRAM accesses in the main thread.

:::

Let's assume that the interrupt fires at, say, scanline 42.
Equipped with [the GB instruction table](https://gbdev.io/gb-opcodes/optables) (see its legend at the bottom), we can plot how many cycles each operation takes, in relation with the PPU's mode:

<Timeline :offset="0" asmFile="lyc_timing/simple_handler.asm">
    <CPUOp op="interrupt" />
    <CPUOp :line="2" />
    <CPUOp :line="3" immediate />
    <CPUOp :line="4" immediate />
    <CPUOp :line="5" />
    <CPUOp :line="8" immediate />
    <CPUOp :line="9" immediate />
    <CPUOp :line="10" immediate io legend="Write to LCDC" />
    <CPUOp :line="11" />
    <CPUOp :line="12" />
</Timeline>

The first 5 cycles do not have an instruction: indeed, calling an interrupt handler is not instantaneous, and the CPU is temporarily busy pushing the program counter (`PC`) to the stack, disabling interrupts, etc.
Then, the actual interrupt handler begins execution.

We can immediately spot a problem: the cycle during which `LCDC` is written to falls in the middle of rendering!
(With only a handful of exceptions, instructions that access memory do so on their very last cycle.)
This is usually undesirable, and could lead to graphical glitches like an OBJ being partially cut off until we write to `LCDC`.

Another problem, less obvious but oh so painful, is how the interrupt handler might interact with the "main thread"'s operation.

### The VRAM access [race condition](https://en.wikipedia.org/wiki/Race_condition)

Accessing VRAM [is not possible during Mode 3](https://gbdev.io/pandocs/Accessing_VRAM_and_OAM).
Thus, when we want to access VRAM, precautions must be taken; the most common is to use the following loop:

@[code asm](lyc_timing/stat_loop.asm)

This loop checks whether `[STAT] & 2` is zero, and exits when it does.
Looking at [documentation for `STAT`](https://gbdev.io/pandocs/STAT#ff41---stat-lcd-status-rw), we can see that the lowest 2 bits report the PPU's mode, and that `[STAT] & 2` is zero for Mode 0 and Mode 1, but not Mode 2 or Mode 3.
So, essentially, this loop waits for Mode 0 or Mode 1, which are both safe to write to VRAM—but it can't be that simple.

<Timeline :offset="111" asmFile="lyc_timing/stat_loop.asm">
    <CPUOp :line="2" immediate io legend="Read from STAT" />
    <CPUOp :line="3" immediate />
    <CPUOp :line="4" />
    <CPUOp op="critical" />
    <CPUOp op="condensed" :cycles="2" />
</Timeline>

Pictured above is the "worst case" for this loop.
As you can see, on the cycle that `STAT` is read, the PPU is still in Mode 0; however, checking for it takes a few cycles, during which we enter Mode 2!

Now, thankfully, Mode 2 is *also* safe for accessing VRAM—but only 16 cycles of it remain.
This is why this loop is said to guarantee 16 "VRAM-safe" cycles: any access performed 17 cycles or more after it would break in this worst case.

Now, what would happen if our interrupt was requested in the middle of this?

<Timeline :offset="111" asmFile="lyc_timing/simple_handler.asm">
    <CPUOp instr="ldh a, [rSTAT]" immediate io legend="Read from STAT" />
    <CPUOp op="interrupt" />
    <CPUOp :line="2" />
    <CPUOp :line="3" immediate />
    <CPUOp :line="4" immediate />
    <CPUOp :line="5" />
    <CPUOp :line="8" immediate />
    <CPUOp :line="9" immediate />
    <CPUOp :line="10" immediate />
    <CPUOp :line="11" />
    <CPUOp :line="12" />
    <CPUOp instr="and STATF_BUSY" immediate />
    <CPUOp instr="jr nz, .waitVRAM" />
    <CPUOp op="critical" />
</Timeline>

Oh no!
The main thread is now trying to access VRAM right in the middle of Mode 3!
This could lead to all sorts of visual bugs.

### A solution?

The solution is not too complicated, at least on paper.
We should be able to use the same `STAT`-checking loop (or at least, a variation of it) inside of the handler.
It works in the main thread, so it should work here as well, right?

Remember that many `STAT` handlers will be much more complicated than the simple example above, so let's draw a diagram with an imaginary handler that would take significantly more time:

<Timeline :offset="111">
    <CPUOp instr="ldh a, [rSTAT]" immediate io legend="Read from STAT" />
    <CPUOp op="interrupt" />
    <CPUOp op="condensed" :cycles="92" />
    <CPUOp instr="ldh a, [rSTAT]" immediate />
    <CPUOp instr="and STATF_BUSY" immediate />
    <CPUOp instr="jr nz, .handlerWait" />
    <CPUOp instr="(Write to LCDC)" op="ldhl" immediate />
    <CPUOp instr="pop hl" />
    <CPUOp instr="pop af" />
    <CPUOp instr="reti" />
    <CPUOp instr="and STATF_BUSY" immediate />
    <CPUOp instr="jr nz, .waitVRAM" />
    <CPUOp op="critical" />
</Timeline>

::: tip

All the instructions between the "Interrupt dispatch" and "Return from interrupt" blocks are the interrupt handler, the rest is in the "main thread".

:::

The `STAT` loop does fix the register being written to during Mode 3; however, once again, the 16 cycles that "main thread" expects to be VRAM-safe overlap with Mode 3.
The problem here is that the write, `pop` and `reti` all take some of those cycles, and the "main thread" is using the value it read from `STAT` during the previous scanline—but that value is now stale.

### Possible fixes

Using what we have learned so far, we can boil down the problem to three factors:

1. Our handler can trigger in the middle of this sequence of events
2. Our handler preserves the stale value read from `STAT` earlier
3. Our handler returns during a time where accessing VRAM is unsafe

It would be enough to get rid of *any* of these, so let's enumerate our options.

#### Dealing with it

It's entirely possible to accept the loss of some of those cycles.
This amounts to assuming less than the usual 16 cycles after such loops.
For example, putting a `STAT`-polling loop just before the last `pop af` and `reti` would have these two eat up 7 cycles, so we are down to 9.

This will quickly become impractical, requiring syncing to the LCD much more often in the main thread.

#### Handler timing

A simple way to prevent those pesky handlers from throwing off our timing is to disable them, with the `di` instruction.
Unfortunately, it can't quite be so simple, as using `di` for this brings its own share of problems.

The most important one is that disabling the handlers like this *delays* their execution!
`STAT` handlers designed to write to hardware regs during HBlank may start doing so during rendering instead; timer interrupts won't trigger as regularly now; and so on.

Using `di` is valid in some cases, but typically not when `STAT` interrupts are involved, due to their fairly strict timing requirements.

An oddly common alternative is to perform all VRAM updates in VBlank handler.
(The reason why it's common especially in early GB games is likely being a carry-over from the NES, where the lack of HBlanks essentially mandates such a setup anyway.)
While this can work, such as for *Metroid II*, it requires significant complexity from having to keep deferring graphical updates.

#### Stale `STAT` read

There is not much that can be done about this one.
The interrupt handler *must* preserve registers, and ...

TOCTTOU

#### Return timing

This is the solution that the rest of this article will explore, as we will see that it makes the least painful compromises out of most use cases.

So, the real solution is to fully exit before the end of HBlank.
There are two ways to do this.
One is to wait for the Drawing phase before waiting for HBlank.
This effectively catches the very start of HBlank, leaving plenty of time to exit.
Here's how the earlier example might look using this method:

@[code asm](lyc_timing/ret_hblank_handler.asm)

See how this method never interferes with VRAM accesses in the main thread, even with the worst possible timing and the shortest of HBlanks:

<Timeline :offset="111" :hblank-length="21" asmFile="lyc_timing/ret_hblank_handler.asm">
    <CPUOp instr="ldh a, [rSTAT]" immediate />
    <CPUOp op="interrupt" />
    <CPUOp :line="2" />
    <CPUOp :line="3" />
    <CPUOp :line="4" immediate />
    <CPUOp :line="5" immediate />
    <CPUOp :line="6" />
    <CPUOp :line="9" immediate />
    <CPUOp :line="10" immediate />
    <CPUOp :line="11" taken />
    <CPUOp :line="18" op="ld-imm16" />
    <CPUOp :line="20" immediate io legend="STAT is tested" />
    <CPUOp :line="21" />
    <CPUOp :line="23" immediate io legend="STAT is tested" />
    <CPUOp :line="24" taken />
    <CPUOp op="condensed" :cycles="42" />
    <CPUOp :line="23" immediate io legend="STAT is tested" />
    <CPUOp :line="24" taken />
    <CPUOp :line="23" immediate io legend="STAT is tested" />
    <CPUOp :line="24" taken />
    <CPUOp :line="23" immediate io legend="STAT is tested" />
    <CPUOp :line="24" />
    <CPUOp :line="26" immediate />
    <CPUOp :line="27" />
    <CPUOp :line="28" />
    <CPUOp :line="29" />
    <CPUOp instr="and STATF_BUSY" immediate />
    <CPUOp instr="jr nz, .waitVRAM" />
    <CPUOp op="condensed" :cycles="16" class="critical" legend="VRAM accesses" />
</Timeline>

Phew! This just barely works.
There are only two cycles to spare! If there were multiple registers that needed updating, you might run into trouble.
Normally, These really short HBlanks are the worst-case scenario that you always fear.
However, in practice, HBlanks are normally much longer, often even longer than the drawing phase.
Using this method, that can actually have unfortunate consequences:

<Timeline :offset="111" :hblank-length="51" asmFile="lyc_timing/ret_hblank_handler.asm">
    <CPUOp instr="ldh a, [rSTAT]" immediate />
    <CPUOp op="interrupt" />
    <CPUOp :line="2" />
    <CPUOp :line="3" />
    <CPUOp op="condensed" :cycles="46" />
    <CPUOp :line="18" op="ld-imm16" />
    <CPUOp :line="20" immediate io legend="STAT is tested" />
    <CPUOp :line="21" taken />
    <CPUOp op="condensed" :cycles="48" />
    <CPUOp :line="20" immediate io legend="STAT is tested" />
    <CPUOp :line="21" />
    <CPUOp :line="23" immediate io legend="STAT is tested" />
    <CPUOp :line="24" taken />
    <CPUOp op="condensed" :cycles="48" />
    <CPUOp :line="23" immediate io legend="STAT is tested" />
    <CPUOp :line="24" />
    <CPUOp :line="26" immediate />
    <CPUOp :line="27" />
    <CPUOp :line="28" />
    <CPUOp :line="29" />
    <CPUOp instr="and STATF_BUSY" immediate />
    <CPUOp instr="jr nz, .waitVRAM" />
    <CPUOp op="condensed" :cycles="16" class="critical" legend="VRAM accesses" />
</Timeline>

This time, when all the processing was done, there was still plenty of time left in the scanline to safely exit.
However, since HBlank was so long, the routine missed the check for the drawing window and wasted an entire scanline waiting for that Drawing -> HBlank transition before it exited.
Not only does this waste precious CPU time, but it also limits how often raster FX can be used throughout the frame.
This method still works fine though, and can be an easy approach if you use Raster FX sparingly.

I'm a bit of a perfectionist, so I usually like to strive for the absolute best method.
In a perfect world, we would precisely know whether we have enough HBlank left to safely exit.
There actually is a way to do that though! You just need to count exactly how long your routine takes, and make sure it always exits during HBlank.
This comes with some caveats though.
Most routines, if they haven't been specifically designed for this method, will take a variable amount of time.
The main things you need to avoid are `if` statements and loops.
Specifically, if statements of this form are problematic:

```asm
    ; test a condition here...

    jr nc, .skip ; skip the next part unless Carry is set

    ; do something here, only if the previous operation set Carry

.skip
    ; continue on with the program.
```

The problem here is that the code following this pattern may be run after a variable number of cycles have passed.
If you need to use an if statement, always make it an if/else statement so that you can waste cycles in the `else` portion and take the same number of cycles.

So now that you're ready to count the cycles of your handler, how long do you need to make the routine? Let's look at some more diagrams to figure this out!

<Timeline :offset="111">
    <CPUOp instr="ldh a, [rSTAT]" immediate io legend="STAT read" />
    <CPUOp op="interrupt" />
    <CPUOp op="condensed" :cycles="105" />
    <CPUOp instr="reti" />
    <CPUOp instr="and STATF_BUSY" immediate />
    <CPUOp instr="jr nz, .waitVRAM" />
    <CPUOp op="condensed" :cycles="16" class="critical" legend="VRAM accesses" />
</Timeline>

Wow! That's a lot of cycles! Here, the routine takes exactly one scanline to complete, so the main thread does its writes at the same moment on the next scanline, with no idea what happened! If you count up all the cyan cycles, you'll see that there are 105 of them, and 109 if you count the `reti`.
This extra time makes it possible to write to two or three registers safely, rather than just one.
If you don't need all that time, you can make it shorter as well:

<Timeline :offset="107" :hblank-length="21">
    <CPUOp instr="ldh a, [rSTAT]" immediate io legend="STAT read" />
    <CPUOp instr="and STATF_BUSY" immediate />
    <CPUOp instr="jr nz, .waitVRAM" />
    <CPUOp op="interrupt" />
    <CPUOp op="condensed" :cycles="84" />
    <CPUOp instr="reti" />
    <CPUOp op="condensed" :cycles="16" class="critical" legend="VRAM accesses" />
</Timeline>

This time, I put the `and` and `jr` before the interrupt, so that when it resumes, it's all ready to start writing to VRAM.
This interrupt routine is 87 cycles long, including the `reti`.
This won't often prove especially useful though, because you never take any time during HBlank to actually do any register writes.
However, you could use this if your routine has a case where it realizes that nothing actually needs to be written, and you can exit earlier.

From those two diagrams, you'll see that the 22 cycles of worst-case HBlank is the time you can use to write to any PPU registers, pop your registers back, and then exit with `reti`.
These 22 cycles are cycle 88 through cycle 109, inclusive.

What if I told you that you could actually have your handler take only 86 cycles? Well, you can!

<Timeline :offset="107" :hblank-length="21">
    <CPUOp instr="ldh a, [rSTAT]" immediate io legend="STAT read" />
    <CPUOp instr="and STATF_BUSY" immediate />
    <CPUOp instr="jr nz, .waitVRAM" />
    <CPUOp op="interrupt" />
    <CPUOp op="condensed" :cycles="83" />
    <CPUOp instr="reti" />
    <CPUOp op="condensed" :cycles="16" class="critical" legend="VRAM accesses" />
</Timeline>

This seems bad, since the first cycle of the red bar, where the main thread may try to access VRAM, is potentially during the Drawing phase! This is also fine though.
All instructions that access memory, whether through an immediate address or using a register pair as a pointer, take multiple cycles to complete.
That's because the first cycle of every instruction is used to fetch the operation code itself.
The memory access that the instruction performs is always in the 2nd, 3rd or 4th cycle of the instruction.
In this situation, the 2nd cycle of the VRAM-accessible time is in HBlank, so this won't actually cause any problems.

## But Wait!

The interrupt latency I showed earlier doesn't actually tell the full story.
Before it even starts to service the interrupt, the system waits for the current instruction to finish.
This is how that might look with the longest allowable routine:

<Timeline :offset="106" :hblank-length="51">
    <CPUOp instr="ldh a, [rSTAT]" immediate io legend="STAT read" />
    <CPUOp instr="and STATF_BUSY" immediate />
    <CPUOp instr="jr nz, .waitVRAM" />
    <CPUOp instr="call SomeFunc" />
    <CPUOp op="interrupt" />
    <CPUOp op="condensed" :cycles="105" />
    <CPUOp instr="reti" />
    <CPUOp op="condensed" :cycles="10" class="critical" legend="VRAM accesses" />
</Timeline>

Here, the first green block shows the system waiting 5 cycles for a `call` instruction to finish. `call` is the longest instruction at 6 cycles, so if the interrupt is requested just after it begins, the system will wait 5 cycles for it to complete.
This seems bad, since the routine exited after the end of HBlank.
However, this is actually fine! Those waiting cycles were not wasted; they were still 5 cycles of work that the main thread got done.
So in the end, the main thread still gets its 20 cycles of VRAM-accessible time.

## Pros and Cons

Thus far, I have presented two very different methods for making safe `LYC` handlers, and each have their pros and cons.

## Double-Busy-Loop

**Pros**
- does not require all code to be constant-time
- does not require tedious cycle-counting
- may exit very early if the routine finishes quickly

**Cons**
- does not provide enough HBlank time to safely write multiple registers
- if the routine takes too long, it may miss mode 3 and waste an entire scanline before exiting

## Cycle-counting

**Pros**
- leaves more time for more complex logic in the routine
- allows enough time during blanking to write to up to three registers
- never takes longer than one scanline

**Cons**
- requires all code to be constant-time
- requires tedious cycle-counting
- always takes close to an entire scanline, even if HBlank starts much sooner

This suggests that the double-busy-loop method is good for extremely simple `LYC` routines that only need to write to one register, or routines that for some reason cannot be cycle-counted.
If you need more time for calculations and more time to write to those registers, you can cycle-count your routine.

But what if you could combine both these methods? Enter the **Hybrid Cycle-Counted Handler™**, a technique I came up with while writing this document.

## Combining Approaches

The goal of this method is to combine the maximum HBlank time that cycle-counting delivers, while still exiting early when HBlank is longer.
Here is an example.
If you've read [DeadCScroll](deadcscroll), you'll recognise this as that tutorial's `STAT` Handler, modified to start at Mode 2 rather than HBlank, and be safe towards VRAM accesses in the main thread.

@[code asm](lyc_timing/hybrid_handler.asm)

Once the handler finishes its logic, the handler delays cycles until it reaches the window then HBlank might start.
With a 5-cycle offset due to a `call`, and the longest possible HBlank, the earliest HBlank might start is cycle 54, so that's the first attempt to read `STAT`.
It keeps checking `STAT` until even in the worst-case scenario, it knows that HBlank will start.
Then, it uses that time to write the scroll registers and exit.
This way, it can still exit early, as long as the HBlank length permits.
This routine takes 104 cycles in the worst-case scenario, but may take as few as 79 if HBlank comes sooner.

The reason that the double-busy-loop method requires checking for Mode 3 but this method does not is that the double-busy-loop method is not cycle-counted, so you might be at the very end of HBlank which is problematic.
Since this method is cycle-counted, you know that if HBlank has begun, you are at or near the start of it.

If we make a similar list of pros and cons for this method, this is what it might look like:

## Hybrid cycle-counting

**Pros**
- may exit very early if HBlank is longer
- allows enough time during blanking to write to up to three registers
- never takes longer than one scanline

**Cons**
- requires all code to be constant-time
- requires tedious cycle-counting

This method can work well in many circumstances, and is especially suited to frequent effects that modify multiple registers and need to exit quickly to avoid taking too much CPU time.
This method can even work reasonably well when used on every scanline through the Mode 2 interrupt.

All three of these methods can generate great-looking effects, but I think the third one is an especially attractive option.

Congrats! You made it to the end of the tutorial! I bet you're tired of reading it, and I'm tired of writing it too.
So thanks for reading, see you next time!

<script>
import { h } from 'vue';

// HACK: we import all of the ASM files here because imports must be static and at top-level
import stat_loop          from '@/../guides/lyc_timing/stat_loop.asm';
import simple_handler     from '@/../guides/lyc_timing/simple_handler.asm';
import ret_hblank_handler from '@/../guides/lyc_timing/ret_hblank_handler.asm';
import hybrid_handler     from '@/../guides/lyc_timing/hybrid_handler.asm';
const instrs = string => string.split(/\r?\n/).map(line => line.split(';')[0].trim());
const ASM_FILES = {
    'lyc_timing/stat_loop.asm':          instrs(stat_loop),
    'lyc_timing/simple_handler.asm':     instrs(simple_handler),
    'lyc_timing/ret_hblank_handler.asm': instrs(ret_hblank_handler),
    'lyc_timing/hybrid_handler.asm':     instrs(hybrid_handler),
};

const SCANLINE_LEN = 114, MIN_HBL_LEN = 21, MAX_HBL_LEN = 51;

const Timeline = {
    props: {
        offset: {
            type: Number,
            required: true,
            validator(value) {
                return value >= 0 && value <= SCANLINE_LEN;
            },
        },
        hblankLength: {
            type: Number,
            default: MIN_HBL_LEN,
            validator(value) {
                return value >= MIN_HBL_LEN && value <= MAX_HBL_LEN;
            },
        },
        asmFile: String,
    },

    render() {
        const MODE_CHANGES = [20, SCANLINE_LEN - this.$props.hblankLength, SCANLINE_LEN];

        const asmFileName = this.$props.asmFile;
        let asmFile;
        if (asmFileName) {
            asmFile = ASM_FILES[asmFileName];
            if (asmFile === undefined) {
                throw new SyntaxError(`Unknown ASM file "${asmFileName}" (did you forget to register it in \`ASM_FILES\`?)`)
            }
        }
        let slots = this.$slots.default(); // The slots we'll be working on (shorthand)

        let opsLegend = {}; // Operations with a legend will be registered in this dict
        let cycles = []; // One entry per row in the table

        let curScanlineCycle = this.$props.offset; // The position inside the current scanline
        let curInstrCycles = 0; // How many cycles are left of the current instruction
        for (let slotIdx = 0; slotIdx < slots.length || curInstrCycles !== 0; ) {
            // Determine this cycle's PPU background color (depending on PPU mode)
            let modeAt = scanlineCycle => {
                if (curScanlineCycle < MODE_CHANGES[0]) {
                    return 2;
                } else if (curScanlineCycle < MODE_CHANGES[1]) {
                    return 3;
                } else {
                    return 0;
                }
            };
            const ppuColorClass = 'ppu-mode' + modeAt(curScanlineCycle);

            let children = [h('td', { class: ppuColorClass }, '' + curScanlineCycle)];

            // Check if a new instruction begins on this cycle
            // If so, push a  for this instruction
            if (curInstrCycles == 0) {
                const slotProps = slots[slotIdx].props;
                const instrInfo = (function () {
                    try {
                        return CPUOp.info(slotProps, asmFile);
                    } catch (e) {
                        if (e instanceof SyntaxError && asmFileName && slotProps.line !== undefined) {
                            e.message += ` (at ${asmFileName}:${slotProps.line})`;
                        }
                        throw e;
                    }
                })();
                const instrName = instrInfo.instr;

                let className = 'cpu-' + (instrInfo.class || 'op');
                if (slotProps.io !== undefined) {
                    className += ' cpu-io';
                }

                let props = {
                    rowspan: instrInfo.cycles,
                    class: className,
                    condensed: slotProps.op === 'condensed',
                };
                let contents = props.condensed ? h('b', {}, '(...)') : instrName && h('code', {}, instrName);

                children.push(h('td', props, contents));

                // If this instruction has a legend, register it
                if (instrInfo.legend) {
                    // Anything with a legend is guaranteed to have a class name
                    opsLegend[className] = {
                        order: Object.keys(opsLegend).length,
                        legend: instrInfo.legend,
                    };
                }

                curInstrCycles = instrInfo.cycles; // Register the new instruction's length
                ++slotIdx; // Go to the next instruction
            }

            cycles.push(h('tr', {}, children));

            curScanlineCycle++;
            if (curScanlineCycle == SCANLINE_LEN) curScanlineCycle = 0; // Next scanline, please!
            curInstrCycles--;

            if (children[1] && children[1].props.condensed) {
                // Special handling for the "condensed" pseudo-instruction
                let instr = children[1];

                // Ensure that skips generate no more than 3 rows
                if (instr.props.rowspan > 3) {
                    // Push a row with a cycle count of "..."
                    // The third row will be generated on the next loop iteration

                    let curColor = () => `var(--ppu-mode${modeAt(curScanlineCycle)})`;

                    // That row's background will be a gradient of the different modes that it goes through.
                    // So for that, we need to compute at which points transitions occur
                    const skippedCycles = instr.props.rowspan - 2; // We don't condense the first nor last rows
                    let cyclesRemaining = skippedCycles;
                    let gradColors = []; // Begin with the current mode's color
                    let i = MODE_CHANGES.findIndex(cycle => cycle > curScanlineCycle);
                    // Iterate through all mode changes in the period
                    while (MODE_CHANGES[i] - curScanlineCycle < cyclesRemaining) {
                        // Register the color before the change
                        const oldColor = curColor();

                        cyclesRemaining -= MODE_CHANGES[i] - curScanlineCycle; // Subtract that distance
                        curScanlineCycle = MODE_CHANGES[i] % SCANLINE_LEN; // Go to the mode change point (and wrap around)
                        i = (i + 1) % MODE_CHANGES.length; // Check out the following mode next

                        // Register the color change
                        const percentage = 100 - parseInt(cyclesRemaining * 100 / skippedCycles);
                        gradColors.push(`${oldColor} ${percentage}%, ${curColor()} ${percentage}%`);
                    }
                    curScanlineCycle = (curScanlineCycle + cyclesRemaining) % SCANLINE_LEN;
                    const style = gradColors.length
                        ? { 'background-image': `linear-gradient(${gradColors.join(', ')})` }
                        : { 'background-color': curColor() };

                    cycles.push(h('tr', {}, h('td', { style }, h('b', {}, '...'))));

                    // Refresh the instruction cell's rowspan and "remaining rows to generate" counts
                    instr.props.rowspan = 3;
                    curInstrCycles = 1; // Only 1 row left to generate (the last one)
                }
            }
        }

        const headings = h('tr', {}, [
            h('th', {}, ['Scanline', h('br'), 'cycle']), // Force a line break to narrow the column
            h('th', {}, 'Instruction'),
        ]);
        return h('figure', { class: 'timeline-figure' }, [
            h('div', { class: 'timeline-legend' }, [
                h('h3', {}, 'Legend'),

                h('table', {}, [
                    h('tr', {}, h('th', { colspan: 2 }, 'PPU Mode')),
                    ...[
                        { id: 2, name: 'OAM scan' },
                        { id: 3, name: 'Drawing' },
                        { id: 0, name: 'HBlank' },
                    ].map(mode => h('tr', {}, [
                        h('td', { class: 'ppu-mode' + mode.id }, '' + mode.id),
                        h('td', {}, mode.name),
                    ])),
                ]),

                h('table', {}, [
                    h('tr', {}, h('th', { colspan: 2 }, 'CPU operation')),
                    ...Object.entries(opsLegend)
                             .sort(([_lc, l], [_rc, r]) => l.order - r.order) // Sort the legend by class names
                             .map(([className, {legend}]) => h('tr', {}, [
                        h('td', { class: className }),
                        h('td', {}, legend),
                    ])),
                ]),
            ]),
            h('table', {}, [
                h('thead', {}, headings),
                h('tfoot', {}, headings),
                h('tbody', { class: 'timeline' }, cycles),
            ]),
        ]);
    },
};

const CPUOp = {
    props: {
        // Either of these is required, and the latter requires the Timeline to pass an `asmFile`
        op: String,
        line: {
            type: Number,
            validator(value) {
                return value > 0; // We count from 1
            },
        },

        immediate: Boolean,
        io: Boolean,
        taken: Boolean,
        'class': String,
        legend: String,
        cycles: Number,
    },

    info(props, asmFile) {
        // If requesting a line from an ASM file, force the instruction from it
        if (props.line !== undefined) {
            if (asmFile === undefined) {
                throw new SyntaxError("ASM line requested, but no ASM file was provided!");
            }
            props.instr = asmFile[props.line - 1];
        }
        // If a specific instr has been given but `op` has not, deduce it
        if (props.instr !== undefined && props.op === undefined) {
            props.op = props.instr.split(/\s/)[0];
        }

        let info = (function() {
            // Here is an index of returned properties; be advised that some override others!
            //
            // class:
            //   If truthy, the class that will be applied (prefixed with "cpu-") to "Instruction" cells (and the legend).
            //   Avoid specifying directly, as then you should specify `legend` as well, which overrides this.
            //   Can be overridden from the props, which then supersedes `legend`'s own override.
            //
            // cycles:
            //   How many cycles this operation is long.
            //
            // fixed:
            //   If truthy, the instruction cannot be tagged "immediate".
            //   Note: some operations have an explicit `fixed: false`; this means they aren't really immediate, but can take an extra cycle for another reason (e.g. accessing `[hl]`)
            //
            // jump:
            //   If truthy, the instruction can be tagged "taken", but not "immediate".
            //
            // instr:
            //   If truthy, the string that will be placed raw (no Markdown) in the "Instruction" column in a <code> span.
            //   Can be overridden from the props.
            //
            // legend:
            //   If truthy, the string that will be printed.
            //   Additionally, if truthy (including if overridden by the props), `class` will be set to the operation's name, except if the operation does IO.
            //   Can be overridden from the props.
            //
            // line:
            //   If set, and an ASM file was specified in the `Timeline`, `instr` will be set to the corresponding line.
            //   Line numbers count from 1 for consistency with code blocks.
            //
            // op:
            //   The operation's name, which determines its intrinsic properties.
            //   Can be overridden from the props.
            //   If not specified, will be deduced from the `instr` prop if that was specified or deduced.
            switch (props.op) {
                case 'and':
                    return { cycles:  1, instr: 'and' };
                case 'bit':
                    return { cycles:  2, instr: 'bit', fixed: false };
                case 'call':
                    return { cycles:  6, instr: 'call', class: 'call', fixed: true }; // An untaken "call" takes 3 cycles, not 5
                case 'cp':
                    return { cycles:  1, instr: 'cp' };
                case 'critical':
                    return { cycles: 16, legend: 'VRAM accesses', fixed: true };
                case 'interrupt':
                    return { cycles:  5, legend: 'Interrupt dispatch', fixed: true };
                case 'jr':
                    return { cycles:  2, instr: 'jr', jump: true };
                case 'ld-imm16':
                    return { cycles:  3, instr: 'ld', fixed: true };
                case 'ldh':
                    return { cycles:  2, instr: 'ldh' };
                case 'ldhl':
                    return { cycles:  2 };
                case 'or':
                    return { cycles:  1, instr: 'or' };
                case 'pop':
                    return { cycles:  3, instr: 'pop', fixed: true };
                case 'push':
                    return { cycles:  4, instr: 'push', fixed: true };
                case 'reti':
                    return { cycles:  4, instr: 'reti', legend: 'Return from interrupt', fixed: true };
                case 'set':
                    return { cycles:  2, instr: 'set', fixed: false };

                case 'condensed':
                    let cycles = props.cycles;
                    if (typeof cycles !== 'number' || !isFinite(cycles)) {
                        throw new SyntaxError('"Skip" CPU op requires a numeric cycle count');
                    }
                    return { cycles, class: 'condensed', class: 'op' };

                default:
                    throw new SyntaxError(`Unknown instruction type "${props.op}"`);
            };
        })();

        // If a line was requested, override the instruction with it
        if (props.instr) {
            info.instr = props.instr;
        }

        // Immediate instructions and taken jumps take one extra cycle
        // FIXME: Normally Vue would correctly type these as Booleans as per `props` above, but it doesn't...
        if (props.immediate !== undefined) {
            if (info.fixed || info.jump) {
                throw new SyntaxError(`CPU op ${props.op} cannot be immediate!`)
            }
            info.cycles++;
        }
        if (props.taken !== undefined) {
            if (!info.jump) {
                throw new SyntaxError(`CPU op ${props.op} is not a jump, it cannot be taken!`);
            }
            info.cycles++;
        }

        // Override the legend if explicitly provided
        if (props.legend) {
            info.legend = props.legend;
        }
        // If the operation has a legend, add its name as a class; unless the operation merely does IO
        if (info.legend && props.io === undefined) {
            info.class = props.op;
        }
        // Override the class if explicitly provided
        if (props.class) {
            info.class = props.class;
        }

        return info;
    },
};

import InlineSvg from 'vue-inline-svg';

export default {
    components: {
        Timeline,
        CPUOp,

        InlineSvg,
    },
}
</script>
<style>
.timeline-figure {
    display: flex;
    flex-flow: row-reverse wrap;
    align-items: center;
    justify-content: space-around;

    /* These are also used for the "condensed" rows' gradients */
    --ppu-mode2: #ffd96680;
    --ppu-mode3: #ff800080;
    --ppu-mode0: #97d07780;
}

.timeline tr { background-color: transparent; }
.timeline td { padding-top: 0; padding-bottom: 0; }

.ppu-mode2 { background-color: var(--ppu-mode2); }
.ppu-mode3 { background-color: var(--ppu-mode3); }
.ppu-mode0 { background-color: var(--ppu-mode0); }

.cpu-op         { background-color: #56b4e980; }
.cpu-call       { background-color: #00ff0080; }
.cpu-critical   { background-color: #cc79a780; }
.cpu-interrupt  { background-color: #009e7380; }
.cpu-reti       { background-color: #0072b280; }
.cpu-io               { background-image: linear-gradient(to top, #f0e442, #f0e442); }
.cpu-io[rowspan="2"]  { background-image: linear-gradient(to top, #f0e442 50%, #00000000 50%); }
.cpu-io[rowspan="3"]  { background-image: linear-gradient(to top, #f0e442 33%, #00000000 33%); }
</style>

---

## File: website/guides/sgb_border.md

# Adding a custom SGB border

This document aims to help developers of DMG-compatible homebrew with adding Super Game Boy borders.

We will see how to:

- Detect whether the program is running on a SGB
- Transfer the border's tiles
- Transfer and display the border's tilemap and palettes

<small>

Written by **sylvie (zlago)**, idea (and minor help) by [valentina (coffee bat)](https://coffeebat.neocities.org/), reviews and improvements by [ISSOtm](https://eldred.fr), [avivace](https://github.com/avivace), and [PinoBatch](https://github.com/pinobatch).

</small>

## Enabling SGB features

Before we can do anything else, we must first specify in the header that this game is aware of SGB features.
Otherwise, the SGB BIOS will ignore any packets we send, and we won't even be able to detect when the program is running on SGB.

To enable SGB features:

- The [SGB flag] must be set to `$03`;
- The [old licensee code] must be set to `$33`.

[SGB flag]: https://gbdev.io/pandocs/The_Cartridge_Header#0146--sgb-flag
[old licensee code]: https://gbdev.io/pandocs/The_Cartridge_Header#014b--old-licensee-code

This can be achieved by passing the `--sgb-compatible` and `--old-licensee 0x33` flags to [`rgbfix`](https://rgbds.gbdev.io/docs/rgbfix.1).

## Packets

The SGB BIOS can be "talked to" via *command packets*, sent bit by bit via the `P1`/`JOYP` register.

An SGB packet consists of:

- a "start" pulse (`P1`=`%xx00xxxx`)
- 128 data pulses (`P1`=`%xx01xxxx` for "1", `P1`=`%xx10xxxx` for "0")
- a "0" pulse (`P1`=`%xx10xxxx`)

You must set `P1` to `%xx11xxxx` between each pulse.

This adds up to 16 bytes of data (LSB first). If a packet doesn't read all 16 bytes, the unused bytes are ignored.

**You should wait 4 frames between each packet and the next.** This gives the SGB BIOS a chance to receive a packet even if it is doing something else time-consuming.

For an example of such routine, see the related Pan Docs entry: [SGB Command Packet on Pandocs](https://gbdev.io/pandocs/SGB_Command_Packet.html).

This guide glosses over a minor detail, as certain packets can be (albeit unccomon) more than 16 bytes.

## TRN

Bulk transfer (TRN) packets tell the SGB to copy the contents of the screen to buffers in Super NES work RAM. The `CHR_TRN` and `PCT_TRN` packets are used to send data for SGB borders.

For a transfer to function properly, you must prepare VRAM and the LCD registers:

1. set `BGP` to `$e4` and `LCDC` to `$91` (screen enabled, BG uses tiles `$8000`-`$8fff` and tilemap `$9800`, WIN and OBJ disabled, BG enabled)
2. set `SCX` and `SCY` to `$00`
3. the tilemap consists of `$00`, `$01`..`$13`, 12 bytes padding (offscreen), `$14`..`$27`, padding, repeat until `$ff` (inclusive)
4. the data you want to send must be loaded at `$8000`-`$8fff`

You can do 1, 2 and 3 via [this snippet](https://github.com/gb-archive/snek-gbc/blob/main/code/init.sm83#L208-L230)

- **You must load the data into VRAM and enable the screen before sending the TRN packet.** The SGB reads TRN payloads from the screen. If rendering is off, there is nothing on the screen to read.
- **You must wait ~8 frames after each TRN** instead of just 4. The SGB BIOS has to finish what it's doing, receive the packet, and then read the screen.

## Detecting SGB

Here's how a SGB detection routine should look like:

1. Wait 12 or more frames for the SGB BIOS to start listening for packets.
2. Send a `MLT_REQ` packet selecting 2 or 4 players (`$89, $01` or `$89, $03`), and wait a couple frames for the SGB to receive the packet.
3. Read the controller. Set `P1` bit 5 to 0, then set `P1` bits 5 and 4 to `%11` (`%xx11xxxx`) to release the key matrix.
4. Read the low nibble (bits 3-0) of `P1`, and look for a value other than `%1111` (which indicates player 1).
5. If player 1 was still found, repeat steps 3 and 4 once more, in case the next read indicates player 2.
6. Optionally turn off multiplayer mode.

If a non-`%1111` value was found in step 4 either time, the program is running on SGB.

A routine like this may be used to detect SGB (modified from [source](https://github.com/gb-archive/snek-gbc/blob/main/code/init.sm83#L167-L196)):

```sm83asm
SGB_Detect:
  ; test for SGB
  di
  call SGB_Wait4Frames
  call SGB_Wait4Frames
  call SGB_Wait4Frames

  ; Send MLT_REQ packet to enable multiplayer
  ld hl, Packets.mltOn  ; send MLT_REQ for 2 players
  call SGB_SendPacket
  call SGB_Wait4Frames

  ; Detect the SGB by checking if SGB responded to MLT_REQ.
  ; Setting P1.5 to low then high advances the selected player.
  ; Setting P1.4 and P1.5 high causes the ICD2 to reflect the
  ; selected player in low bits of P1.
  ld b, 4              ; Number of attempts
  ld c, LOW(rP1)       ; Address to write
  .loop
    ld a, P1F_4        ; Try to advance player
    ldh [c], a
    ld a, P1F_4|P1F_5  ; Set P1 to return the selected player
    ldh [c], a
    ; In case this is DMG and not SGB, let the input lines settle
    ; for a few cycles before reading P1 again
    call SGB_Wait4Frames.knownRet

    ldh a, [c]         ; Player 1 has A.0 = 1; players 2 and 4 have A.0 = 0
    cpl                ; Invert this
    and %00000001      ; Keep only bit 0, which is 0 for player 1 or nonzero for players 2 and 4
    jr nz, .done
    dec b              ; Keep trying until we've cycled through all players
    jr nz, .loop
  .done
  ; After this loop, A is $01 for SGB or $00 for not SGB.  Remember this
  ld [wIsSGB], a

  ; (Optional) Disable multiplayer
  ld hl, Packets.mltOff  ; send MLT_REQ for 2 players
  call nz, SGB_SendPacket
  ret

SGB_Wait4Frames:
  ld bc, -(456 * 154 / 16 * 4)
  .loop
    inc c  ; inner loop takes 16 T-states per iteration
    jr nz, .loop
    inc b
    jr nz, .loop
  .knownRet
  ret
```

### SGB detection notes

- It would be a good idea to save somewhere in RAM whether the game is running on an SGB capable device or not, such that if you wish to change the border mid-gameplay, you won't have to perform SGB detection again.  The sample code stores it in `wIsSGB`.
- **If you wish to only use 1 controller for the game, you will have to send another `MLT_REQ` to disable multiplayer** (`$89, $00`)
- `SGB_Wait4Frames` above uses busy waiting. Depending on the structure of your initialization code, you can change it to use vblank interrupts or `di`+`halt` instead.

## Border limitations

An SGB border has:

- 255 tiles + 1 transparent tile (preferably tile #0)
- 3 palettes of 15 (+ 1 transparent) colors, (up to 45 solid colors total)
- a 256×224-pixel tilemap (there's a bit more to this, see [notes](#notes))

## Converting borders

With a recent version of [superfamiconv](https://github.com/Optiroc/SuperFamiconv):

```sh
superfamiconv -v -i input.png -p output.pal -t output.4bpp -P 4 -m output.pct -M snes --color-zero 0000ff -B 4
```

- `--color-zero` should be the color that your image for transparency, in my case it was blue.
	* If your image has an alpha channel, it can be set can also be set to `00000000` to use the actual transparent color; however, this may cause some issues.
- `-v` is optional, for showing details of the conversion process
- You can add a row of the transparent color at the top of the image to force superfamiconv to make it tile #0, then `incbin "output.pct", 64` to leave out that row.
- `-P 4` sets the base palette to the 4th one, and **SGB borders use SNES palettes 4, 5, and 6.** as of writing this, this option only works if you built superfamiconv from source.

## Uploading borders

As stated before, the SGB border consists of tile data, picture data, and palette data. These are split across 2-3 packets:

- `CHR_TRN` (`$99`) is used to send 4KiB of tile data.

	* since the border can use up to 8KiB of tiles, bit 0 of the second byte specifies which "half" you're sending
		- `$99, $00` if the screen is loaded with the first 4KiB of tile data
		- `$99, $01` if the screen is loaded with the second 4KiB of tile data

- `PCT_TRN` (`$a1`) is used to send the picture and palette data. it also swaps the border, generally a good idea to send it after the tile data[^1]

	* assuming tiles `0`-`255` use VRAM from `$8000` to `$8fff`:
		- the picture data must be at `$8000`-`$873f` (last 64 bytes are _usually_ offscreen, see [notes](#notes))
		- palette data must be at `$8800`-`$885f`
		- everything else is ignored
			* how you skip putting data at `$8740`-`$87ff` is up to you, I prefer doing separate copies, others prefer copying tilemap and palette data in one go, with the area between them padded.

See also the related Pan Docs entry: [SGB Command Border](https://gbdev.io/pandocs/SGB_Command_Border.html).

[^1]: You can send a `CHR_TRN` [up to \~60 frames](https://github.com/pinobatch/little-things-gb/blob/b11b554d73c48a0f54fee0df31e59eb83806fcb4/sgbears/docs/long_story.md) after the `PCT_TRN` for it to apply to the current border, but not all emulators will emulate this. It's fine to just pretend `CHR_TRN`s must go before `PCT_TRN`.

## Notes

1. You can set the first row of tiles to your transparent color to force superfamiconv to put the transparent tile as the 1st tile, however you must then exclude 64 bytes of the tilemap (`incbin "border.pct"` -> `incbin "border.pct", 64`)

2. SGB BIOS reserves palettes 4 through 6 for borders. If you really know what you're doing, you may be able to use palette 0 (the gameplay palette) for animated borders. You will probably have to edit the border in a tile editor such as YY-CHR, as there aren't yet any other tools for that.

3. When the SNES lags, scanline 225 of the SGB border will be visible! You can set the topmost row of the 29th row of tiles to black to hide this.

4. If this doesn't work for you, you can ask for help on the [gbdev](https://gbdev.io/chat.html) channels.