1. Project Overview & Quickstart (gbdev/chat.html)
chat.html
Open-source repository gbdev/chat.html
Repository Details
- Repository: gbdev/chat.html
- Primary Language: Code
Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.
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. Powered by Vuepress 2.x
You need Node (> 14.x) and yarn.
# 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 buildSee 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) is not committed: it's generated from the awesome-gbdev README by yarn build-resources. The website-only top of the page (front matter, videos, notes) lives in 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):
# 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.mdyarn 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):
# 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 buildCI
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 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. 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.",
"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 list thing.
This project is open source and community-lead. Come contribute!
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 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 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 Developmentcommunity
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 GitHub, Mastodon, BlueSky, Twitter and Discord.
Projects
Here's what we are up to:
The single, most comprehensive technical reference to Game Boy available to the public.
[Game Boy CPU Opcode tables](https://gbdev.io/gb-opcodes/optables)
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!
Rednex Game Boy Development System: the de-facto ASM development toolkit for the Game Boy and Game Boy Color.
hardware.inc - Standard include file containing Game Boy hardware definitions for use in RGBDS projects.
rgbds-live - A live Game Boy programming environment in the browser, allowing for realtime assembly programming with RGBDS.
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 (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.
GBDK 2020 Star
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
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.
Homebrew Hub
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.
The Game Boy Archive
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.
Game Boy Camera Club
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.
For Game Boy Advance development, check gbadev.net.
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) 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.
:::
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").
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.
:::
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_NAMEis the name of the AI tool or frameworkMODEL_VERSIONis 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 sparseReferences
Adapted from Linux Kernel - AI Coding Assistants,
some proposals in General Resolution: LLM usage in Debian and
SF Conservancy - Recommendations When Using LLM-backed Generative AI Systems for FOSS Contributions.
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.
- IRC:
#gbdevon Libera.chat is bridged to the Discord chat.#gbdev-ot,#gbdev-casual,#gbdev-hw,#gbdev-meta,#gbdev-asm,#gbdev-gfx,#gbdev-emudev,#gbdev-romhackingand#gbdev-researchare 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 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.
:::
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.
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.
- 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.
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 - Repository - Beginner issues
RGBDS
The de-facto standard assembly toolchain for the Nintendo Game Boy & Game Boy Color.
Technologies and tools: C, C++, Assembly, Rust
Homepage - Repository - Beginner issues
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 - Repository (backend) - Repository (frontend)
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 - Repository - Beginner issues
References
File: website/donate.md
Donations and bounties
Donate
You can donate on our Open Collective or on GitHub sponsors. You can send a one-time donation of any amount or join the collective as backer, donating monthly.
::: tip WHY OPENCOLLECTIVE
The 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 is our fiscal host, providing us the financial and legal infrastructure to receive donations and spend money.
:::
It's also possible to donate specifically towards a project 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.
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 [email protected] 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
- On the gbcompo23 dispute - asie
- https://www.reddit.com/r/gbstudio/comments/1pg8r8q/comment/nszxqdn
0. gbdev.io, Homebrew Hub and gbcompo events
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 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, 2023, 2025) 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.
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 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 and June 2024. 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. 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.
Create original games, demos, homebrews tools and music for the original Game Boy (Color) and compete for glory (and up to US$ 3,829 in prizes)!
Results
Full leaderboards are available here. Top rated entries are playable here while the complete list can be found on Homebrew Hub and Itch.io. A zip file can be downloaded from GitHub.
Games:
- Unearthed - BenJelter
- Rhythm Land - martendo
- Shock Lobster - tbsp
- Dango Dash - Rebusmind
- Rebound - DevEd
- Core Machina - Pearacidic
- Sushi Nights - Zalo
- MARLA and the Elemental Rings - Geek_Joystick
- Porklike GB - binji
- GB Corp - Dr. Ludos
- Dawn Will Come - eishiya
- <corrib75> - gearfo
- El Dueloroso - Adriรกn JG
- Glory Hunter - 2think design studio
- Renegade rush - quinnp
- Fix My Heart - Filipe Bianchin
- GBCspelunky - sukus
Winning entry for the Music category: Zilogized - Kabcorp
Winning entry for the Tools category: 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.
Donations from Individuals
Toxa,
Carles Castillo,
Dave VanEe,
Chris Maltby,
William Bettridge-Radford,
Josh Frisby,
Sanqui
Partners
Community
Come join us on Discord or others for help getting started, and head over to 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, Game Boy technical documentation
- Awesome Game Boy Development list of resources
- Especially relevant is the "Software Development" paragraph
- How to choose between C and ASM for GB?
- RGBDS (ASM language)
- GBDK (C language)
- GBStudio (no coding required)
Prize pool
The current prize pool is and will be split according to this document.
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 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 (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
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).
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.
GBC features are allowed.
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.
Teams are allowed (award will be split).
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.
ROM Hacks are not allowed.
NSFW content is not allowed. Please keep it safe for work and avoid anything overly edgy or distasteful.
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).
Judges can submit entries, but they are not eligible for prizes.
Only some official MBC chips (MBC1, MBC2, MBC3 and MBC5, as well as no MBC at all) are allowed. The only exceptions are:
- 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.)
- 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.)
- The variants MBC30 and MBC1M are accepted, as they have been used in licensed games.
- 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.
Entries cannot rely on extra hardware or add-on devices, they must be playable without a specific setup.
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, ...
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 for further details.
To be eligible for the OSS bonus, the code must be licensed under an "Open Source" license (any of the "Free" license listed here 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.
The entry must be submitted on the itch.io jam page.
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)
- 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.
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).
Multiple submissions are allowed.
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:
- Gameplay - How entertaining are the gameplay mechanics? How fun is the game?
- Technical - How innovative is it from a technical perspective? Does it push the hardware to the limit?
- Originality - How refreshing is it from a non-technical perspective? Does it have a unique design / mechanics?
- Graphics - How impressive are the art, animation and visual effects?
- 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, Proximity Sound, untoxa, bbbbbr, Sanqui, aaaaaa123456789, ISSOtm
- Optix, 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.
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. Anybody can contribute by donating.
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, Core Machina, Unearthed
- Music cartridges (1 prize)
- Examples: Zilogized, 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.
- Examples: Back to Color, Is that a demo in your pocket?, Cute Demo CGB.
- Tool (1 prize)
- Examples: Bannerprinter, On-Board GB Tile Creator, StopWatch.
The rankings are decided according to the judging criteria.
Community
Come join us on Discord for help getting started (or find a team), and head over to 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.
Resources
Here is a curated list tools and documentation to get you started.
- General links
- The Game Boy, a hardware autopsy (Video)
- The Ultimate Game Boy Talk (Video)
- Pan Docs, Game Boy technical documentation
- Awesome Game Boy Development list of resources
- Especially relevant is the "Software Development" section
- How to choose between C and ASM for GB?
- RGBDS (ASM language)
- GBDK (C language)
- GBStudio (no coding required)
- GBSDK (C + RGBDS ASM, without a standard library)
- Documentation
- Source for a game and another game built with GBSDK
Sponsors
- Main sponsor: Incube8Games
- Gold sponsors: FerranteCrafts, Macho Nacho
- Silver sponsors: Bitmap Soft, Broke Studio, Yastuna Games
- Bronze sponsors: insideGadgets
Interested in sponsoring? Shoot us a DM.
Rules
Prizes and Judging
- The submitted entry must implement / interpret the jam theme (applies to Game category only).
- 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).
- Judges can submit entries, but the entries won't be eligible for prizes. Judges won't score the entries they submitted.
- 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 for further details.
- To be eligible for the OSS bonus, the code must be licensed under an "Open Source" license (any of the "Free" license listed here 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.
- 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.
- Shipment of physical prizes may not be available to all countries, they are subject to shipping ability of the prize donors.
Hardware Criteria
- 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).
- 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.
- 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).
- 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.
- Only some official MBC chips (MBC1, MBC2, MBC3 and MBC5, as well as no MBC at all) are allowed. The only exceptions are:
- 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.)
- 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.)
- The variants MBC30 and MBC1M are accepted, as they have been used in licensed games.
- 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.
- Entries cannot rely on extra hardware or add-on devices, they must be playable without a specific setup.
- 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
Teams are allowed (award will be split).
ROM Hacks are not allowed.
NSFW content is not allowed. Please keep it safe for work and avoid anything overly edgy or distasteful.
The entry must be submitted on the itch.io jam page.
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)
- 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.
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).
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:
- Gameplay - How entertaining are the gameplay mechanics? How fun is the game?
- Technical - How innovative is it from a technical perspective? Does it push the hardware to the limit?
- Originality - How refreshing is it from a non-technical perspective? Does it have a unique design / mechanics?
- Graphics - How impressive are the art, animation and visual effects?
- Audio - How good are the music and sound effects?
- 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
Results
You can find all submissions in Homebrew Hub and in this GitHub repository.
Music
- 8bit Sunset (GameBoy Music) - Kabcorp
- Gb Compo 23 โ Music Cart โ Tune In! - beatscribe
- Monkeys on Mars - Chavez.funktion
- shared by
- The Infernodome โ Original GB SongROM! - EmperorJub
- No time for art sorry (Music Cart) - incognitio
- Re: Cycle - sloopygoop
Demoscene
- Did Somebody Say Demo? - VL2MSTUDIO
- DDDDDEEMO(N) - Lillie_chippie
Tools
- Monster Orc-arina - Pearacidic
- Dungeon Master - joaobapt
- WeekPlanner - CreativaGS
- shared by
- Iron Cor โ Stainless - Elvies
- Nyan note - Arky750
- witch sound tracker - Arky750
- SGB SOU_TRN demo - cloudscomputing
Games
Overall
| # | Name | Overall | Gameplay | Technical | Originality | Graphics | Audio | Theme |
|---|---|---|---|---|---|---|---|---|
| 1 | Hermano โ ยง | 4.0 | 4.17 | 4.0 | 3.5 | 4.33 | 3.83 | 4.17 |
| 2 | Feed IT Souls โ | 3.92 | 4.17 | 3.5 | 3.83 | 3.83 | 3.67 | 4.5 |
| 3 | Enceladus โ | 3.75 | 3.5 | 3.0 | 3.83 | 3.67 | 3.67 | 4.83 |
| 5 | NUNYA โ | 3.58 | 3.33 | 3.0 | 3.17 | 4.33 | 3.67 | 4.0 |
| 6 | Ghost of the Arcade | 3.42 | 3.33 | 3.5 | 3.33 | 3.67 | 3.33 | 3.33 |
| 7 | Kaiju Kai Kai | 3.33 | 3.0 | 2.67 | 3.5 | 3.5 | 2.83 | 4.5 |
| 8 | Chantey (Prologue) | 3.25 | 2.83 | 3.0 | 3.67 | 3.83 | 4.5 | 1.67 |
| 9 | Slime Trials ยง | 3.06 | 3.83 | 3.17 | 3.67 | 2.33 | 2.83 | 2.5 |
| 10 | Hidden Gems | 3.06 | 4.0 | 3.0 | 4.5 | 2.67 | 3.0 | 1.17 |
| 11 | Abducted ยง | 2.94 | 2.83 | 3.17 | 3.83 | 3.83 | 1.17 | 2.83 |
| 12 | Slayer the Hawk | 2.86 | 2.67 | 3.5 | 4.17 | 2.67 | 1.5 | 2.67 |
| 13 | EXTERMINATOR | 2.83 | 2.67 | 2.67 | 3.17 | 3.0 | 2.83 | 2.67 |
| 14 | Imperium Strike Force | 2.69 | 3.17 | 2.83 | 2.33 | 3.17 | 2.67 | 2.0 |
| 15 | 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.
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. Anybody can contribute by donating.
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, Feed IT Souls, Enceladus
- Music cartridges (1 prize)
- Examples: 8bit Sunset, 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.
- Examples: Back to Color, Is that a demo in your pocket?, Cute Demo CGB.
- Tool (1 prize)
- Examples: Monster Orcarina, On-Board GB Tile Creator, Witch Sound Tracker.
The rankings are decided according to the judging criteria.
Community
Come join us on Discord for help getting started (or find a team), and head over to 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 and Mastodon.
Resources
Here is a curated list tools and documentation to get you started.
- General links
- The Game Boy, a hardware autopsy (Video)
- The Ultimate Game Boy Talk (Video)
- Pan Docs, Game Boy technical documentation
- Awesome Game Boy Development list of resources
- Especially relevant is the "Software Development" section
- How to choose between C and ASM for GB?
- RGBDS (ASM language)
- GBDK (C language)
- API docs: Getting Started
- Examples
- Docs, links and tools
- ZGB: a game engine using GBDK / CrossZGB (an updated fork of ZGB)
- Dedicated Discord
- "How-to make a Gameboy Game" tutorial series
- GBStudio (no coding required)
- GBSDK (C + RGBDS ASM, without a standard library)
- Documentation
- Source for a game and another game built with GBSDK
Sponsors
Rules
JAM THEME: None (do whatever you want!)
Prizes and Judging
- 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).
- Judges can submit entries, but the entries won't be eligible for prizes. Judges won't score the entries they submitted.
- 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 for further details.
- To be eligible for the OSS bonus, the code must be licensed under an "Open Source" license (any of the "Free" license listed here 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.
- 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.
- Shipment of physical prizes may not be available to all countries, they are subject to shipping ability of the prize donors.
- The submitted entry must implement / interpret the jam theme (applies to Game category only).
Hardware Criteria
- 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).
- 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.
- 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).
- 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.
- Only some official MBC chips (MBC1, MBC2, MBC3 and MBC5, as well as no MBC at all) are allowed. The only exceptions are:
- 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.)
- 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.)
- The variants MBC30 and MBC1M are accepted, as they have been used in licensed games.
- 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.
- Entries cannot rely on extra hardware or add-on devices, they must be playable without a specific setup.
- 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
Teams are allowed (prizes will be split).
ROM Hacks are not allowed.
NSFW content is not allowed. Please keep it safe for work and avoid anything overly edgy or distasteful.
The entry must be submitted on the itch.io jam page.
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)
- 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.
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).
It is allowed to submit an entry to multiple events.
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.
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:
- Gameplay - How entertaining are the gameplay mechanics? How fun is the game?
- Technical - How innovative is it from a technical perspective? Does it push the hardware to the limit?
- Originality - How refreshing is it from a non-technical perspective? Does it have a unique design / mechanics?
- Graphics - How impressive are the art, animation and visual effects?
- 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
- Tomato-do โ
- GBC Pip-Boy
- GB Tile Editor
- Still Got the Blues
Music
- Neon Nova โ
- DMG Keygen โ
Demoscene
No prize was awarded in the demoscene category due to lack of qualifying entries.
Past editions
File: website/meetings.md
sidebar: false
Meetings minutes
File: website/newsletter.md
sidebar: false editLink: false
Newsletter
File: website/privacypolicy.md
sidebar: false editLink: false
Privacy Policy
We use a self hosted instance of Matomo, 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?)
- 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.
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".
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
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:
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 and/or chat with us about it!
Naming
RGBASM accepts a lot of symbol names:
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 theldhinstruction.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's
rXXXconstants.
Best practices
Avoid hardcoding things. This means:
No magic numbers.
ld a, CURSOR_SPEEDis much more obvious thanld 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 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, however the jump does not have to be to $0150 (example).
rstvectors and interrupt handlers obviously need to be at the corresponding locations.- RGBDS presently does not allow forcing different sections to be in the same bank. 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), but if that option is unavailable, the only alternative is to explicitly declare them with the sameBANK[]attribute. (In which case it's advisable to add anassert BANK("Section A") == BANK("Section B")line.)
If you need some alignment, prefer
ALIGN[]to forcing the address. A typical example is OAM DMA; for that, preferSECTION "Shadow OAM", WRAM0,ALIGN[8]over e.g.SECTION "Shadow OAM", WRAM0[$C000].
Allocate space for your variables using labels +
ds& co instead ofequ. 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. equallocation is equivalent to hardcoding section addresses (see above), whereas labels are placed automatically by RGBLINK.- Labels support
BANK()and many cool other features! - Labels are output in
mapandsymfiles.
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, put things in
ROMXby default.ROM0space 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).
farcallis a good way to make your code really spaghetti.Don't clear RAM at init! Good debugging emulators will warn you when you're reading uninitialized RAM (BGB 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, for example to make loops stand out like in higher-level languages. However, a lot of people don't do this, 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; 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 and 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.
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.
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):
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:
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 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:
- A double-buffering system that holds the data that feeds the HBlank handler
- 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).
โโโโโโโโโ โโโโโโโโโ
โ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.
โโโโโโโโโ โโโโโโโโโ
โ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:
Draw-->โโโโโโโโโ Fill-->โโโโโโโโโ
Ptr โBuffer โ Ptr โBuffer โ
โA โ โB โ
โ โ โ โ
โ โ โ โ
โ โ โ โ
โ โ โ โ
โ โ โ โ
โโโโโโโโโ โโโโโโโโโBecomes:
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:
; 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],bTo use a pointer, that code looks like this:
; 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:
; swap the contents of each 'pointer' (11 cycles)
ldh a,[hFillBuffer]
ldh [hDrawBuffer],a
xor $02
ldh [hFillBuffer],aAnd to use a pointer, we only need to do this:
; 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:
ldh a,[hDrawBuffer]
ld h,a
ld l,0
; set the scroll registers
ld a,[hl+]
ldh [rSCY],a
ld a,[hl+]
ldh [rSCX],aIt'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.
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
retiNotice 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 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 0.4.2. The only dependency is hardware.inc. All of the effects are shown here 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 secondsLightDelay: this shows a light-colored blank screen for a few secondsDarkDelay: 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 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.
This appearance effect 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.
You can use this system to make a racing game similar to F-1 World Grand Prix II or Wacky Races. 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 image and ISSOtm for peer review!
License
This was released for educational purposes and so is placed in the Public Domain. See 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.
::: 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, you may know about a feature called OAM DMA.
OAM DMA is a convenient feature that allows quickly updating the on-screen "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 and a symbol from hardware.inc):
ld a, HIGH(OAMBuffer)
ldh [rDMA], a ; $FF46
ld a, 40
DMALoop:
dec a
jr nz, DMALoop
retThe 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:
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
retNow, 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).
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.
call DMAHook
ldh [c], a ; A write to `rDMA`, set up by DMAHook
ld a, 40
DMALoop:
dec a
jr nz, DMALoop
retProvided 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 :
DMAHook:
;; Custom code, do whatever you want, it's VBlank time!
; ...
ld c, LOW(rDMA) ; $46
ld a, HIGH(OAMBuffer)
retDMAHook 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 (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:
- Acquire ACE in the "source" game
- Perform cartswap, insert the "victim" game
- "Pseudo-initialize" the victim
- Place the modified DMA handler in HRAM
- Transfer control back to the victim's ROM
- ????
- 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, 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.
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.asmjp DMAHook PostDMAHook: ldh [c], a ld a, 40 DMALoop: dec a jr nz, DMALoop jp hlasmpop 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
pops 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.asmldh [rDMA], a ld a, 40 DMALoop: dec a jr nz, DMALoop retThey can still be patched by overwriting the
ld a, 40instead, and using e.g. thebregister for the loop:asm{1-2,4}call DMAHook ldh [c], a ; Write to rDMA DMALoop: dec b jr nz, DMALoop retThen
DMAHookneeds to return withbadditionally 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) retHowever, if the OAM buffer address passed to the function (in
a) is not static,push afandpop afwill have to be used instead ofld 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 and ISSOtm
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:
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.
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 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.
:::
::: tip TERMINOLOGY
We'll reference a few terms throughout this tutorial; here are brief explanations of them:
- SoC: 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 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
called, 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:
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:
- Register an interrupt by setting
LYCto the desired scanline - When that scanline begins, the STAT interrupt handler will automatically be called
- Perform your chosen effect by modifying PPU registers
- 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=LYCinterrupts 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
STATin 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:
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:
::: 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 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 (see its legend at the bottom), we can plot how many cycles each operation takes, in relation with the PPU's mode:
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.
Thus, when we want to access VRAM, precautions must be taken; the most common is to use the following loop:
This loop checks whether [STAT] & 2 is zero, and exits when it does.
Looking at documentation for STAT, 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.
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?
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:
::: 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:
- Our handler can trigger in the middle of this sequence of events
- Our handler preserves the stale value read from
STATearlier - 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:
See how this method never interferes with VRAM accesses in the main thread, even with the worst possible timing and the shortest of HBlanks:
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:
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:
; 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!
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:
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!
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:
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, 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.
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!
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
Written by sylvie (zlago), idea (and minor help) by valentina (coffee bat), reviews and improvements by ISSOtm, avivace, and PinoBatch.
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.
This can be achieved by passing the --sgb-compatible and --old-licensee 0x33 flags to rgbfix.
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=%xx01xxxxfor "1",P1=%xx10xxxxfor "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.
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:
- set
BGPto$e4andLCDCto$91(screen enabled, BG uses tiles$8000-$8fffand tilemap$9800, WIN and OBJ disabled, BG enabled) - set
SCXandSCYto$00 - the tilemap consists of
$00,$01..$13, 12 bytes padding (offscreen),$14..$27, padding, repeat until$ff(inclusive) - the data you want to send must be loaded at
$8000-$8fff
You can do 1, 2 and 3 via this snippet
- 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:
- Wait 12 or more frames for the SGB BIOS to start listening for packets.
- Send a
MLT_REQpacket selecting 2 or 4 players ($89, $01or$89, $03), and wait a couple frames for the SGB to receive the packet. - Read the controller. Set
P1bit 5 to 0, then setP1bits 5 and 4 to%11(%xx11xxxx) to release the key matrix. - Read the low nibble (bits 3-0) of
P1, and look for a value other than%1111(which indicates player 1). - If player 1 was still found, repeat steps 3 and 4 once more, in case the next read indicates player 2.
- 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):
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
retSGB 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_REQto disable multiplayer ($89, $00) SGB_Wait4Framesabove uses busy waiting. Depending on the structure of your initialization code, you can change it to use vblank interrupts ordi+haltinstead.
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)
Converting borders
With a recent version of superfamiconv:
superfamiconv -v -i input.png -p output.pal -t output.4bpp -P 4 -m output.pct -M snes --color-zero 0000ff -B 4--color-zeroshould 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
00000000to use the actual transparent color; however, this may cause some issues.
- If your image has an alpha channel, it can be set can also be set to
-vis 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", 64to leave out that row. -P 4sets 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, $00if the screen is loaded with the first 4KiB of tile data$99, $01if the screen is loaded with the second 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
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-255use VRAM from$8000to$8fff:- the picture data must be at
$8000-$873f(last 64 bytes are usually offscreen, see notes) - palette data must be at
$8800-$885f - everything else is ignored
- how you skip putting data at
$8740-$87ffis up to you, I prefer doing separate copies, others prefer copying tilemap and palette data in one go, with the area between them padded.
- how you skip putting data at
- the picture data must be at
- assuming tiles
See also the related Pan Docs entry: SGB Command Border.
[^1]: You can send a CHR_TRN up to ~60 frames 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_TRNs must go before PCT_TRN.
Notes
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)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.
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.
If this doesn't work for you, you can ask for help on the gbdev channels.