Skip to content

Add DMG palette cycling with 'C' key#411

Open
Santitub wants to merge 7 commits intoBaekalfen:masterfrom
Santitub:palette_cycling
Open

Add DMG palette cycling with 'C' key#411
Santitub wants to merge 7 commits intoBaekalfen:masterfrom
Santitub:palette_cycling

Conversation

@Santitub
Copy link

What does this PR do?

Adds a palette cycling feature for DMG mode. Users can press 'C'
to switch between 4 predefined color palettes during emulation.

Available palettes:

  • Gray (Default) — Original PyBoy palette
  • Classic Green (DMG) — Original Game Boy green tint
  • Parchment — Warm beige tones, easy on the eyes
  • Mossy — Muted green with earthy tones

Changes:

  • pyboy/utils.py — Added CYCLE_PALETTE event
  • pyboy/pyboy.py — Added palette list, cycling method, event handling
  • pyboy/plugins/window_sdl2.py — Mapped 'C' key
  • tests/test_palette_cycling.py — Added tests

Notes:

  • Only works in DMG mode. CGB mode is intentionally ignored
  • No changes to lcd.py or any core emulation logic
  • Tested with pre-commit hooks

… gameplay by pressing the 'C' key. This feature only applies to DMG (non-CGB) mode.

What does this PR do?

Adds a palette cycling feature for DMG mode that allows users to switch
between 4 predefined color palettes by pressing the 'C' key during emulation.

Available palettes:

  1. Gray (Default)         — Original PyBoy palette
  2. Classic Green (DMG)    — Original Game Boy green tint
  3. Parchment              — Warm beige tones, easy on the eyes
  4. Mossy                  — Muted green with earthy tones

Changes made:

  pyboy/utils.py
    - Added "CYCLE_PALETTE" event to WindowEvent enum.

  pyboy/pyboy.py
    - Added DMG_PALETTES and DMG_PALETTE_NAMES lists.
    - Added _current_palette_idx tracking in __init__.
    - Added _cycle_palette() method that updates BGP, OBP0 and OBP1 palette registers.
    - Added CYCLE_PALETTE event handling in _handle_events().

  pyboy/plugins/window_sdl2.py
    - Mapped 'C' key to CYCLE_PALETTE event in KEY_UP dictionary.

  pyboy/plugins/window_open_gl.py
    - Mapped 'C' key to CYCLE_PALETTE event in _glkeyboard() method.

Notes:

  - Only works in DMG mode. CGB mode is intentionally ignored.
  - No changes to lcd.py or any core emulation logic.
  - Palette changes are applied by updating PaletteRegister.palette_mem_rgb
    and forcing a lookup table recalculation.
  - New palettes can be easily added to the DMG_PALETTES list.
Updated comments for clarity regarding palette checks and state saving/loading errors.
Copy link
Owner

@Baekalfen Baekalfen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! It's a good beginning, but it needs a little work

pyboy/pyboy.py Outdated
Comment on lines 269 to 274
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be an obvious use of a dictionary instead?

pyboy/pyboy.py Outdated
Comment on lines 305 to 309
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate code

pyboy/pyboy.py Outdated
Comment on lines 654 to 656
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dictionaries are ordered in Python. I'd think we could make something with iterators that is more elegant

pyboy/pyboy.py Outdated
Comment on lines 670 to 671
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need a range, you can iterate bgr_palette directly.

pyboy/pyboy.py Outdated
Comment on lines 670 to 675
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a helper-function in PaletteRegister that takes the palette and forces the update internally.

Comment on lines 24 to 31
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to do it "wrong" like all other tests, than to improve just a single test.

Otherwise the PR should be a refactor of all tests.

Comment on lines 34 to 39
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for testing strategy, it's important to test boundary conditions. The middle of a range is not as interesting.

Always test the initial case, and the end case -- i.e. the wrap around. That's where the bugs usually happen.

Comment on lines 42 to 45
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge this with the one above

Comment on lines 48 to 52
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather check this end-to-end, through a screen image. You can check a few pixel values. That is what counts. We don't want to test specific internal state of an object.

Also, this wouldn't work when PyBoy is compiled. Screen images will.

Comment on lines 63 to 66
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this. Use the any_rom_cgb fixture.

@Santitub
Copy link
Author

I think I fixed all that you mentioned

@Santitub Santitub requested a review from Baekalfen February 17, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments