Batocera Display
ASecurityUse when working on how games look on a Batocera cabinet: CRT and other shaders, shaderset resolution and per-game shader overrides, bezel/overlay artwork and alignment, generating missing .info sidecars, filtering unusable bezels, pinning the display resolution, or diagnosing moire and cropped/misaligned game viewports inside bezels. Covers why a shaderset seems invalid, why .info top values are not alpha coordinates, and why Mega-Bezel per-game art fails. Not for performance/framerate tunin...
- 4 stars
- 0 votes
- 0 copies
- 2 views
- Added September 5, 2026
Works with
Security analysis
100/100Pro scans all 6 files and shows the line behind each finding
npx -y skills add t3chnaztea/batocera-skills --skill batocera-display --agent claude-codeAre you the author of Batocera Display?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/t3chnaztea-batocera-display)---
name: batocera-display
description: >-
Use when working on how games look on a Batocera cabinet: CRT and other
shaders, shaderset resolution and per-game shader overrides, bezel/overlay
artwork and alignment, generating missing .info sidecars, filtering
unusable bezels, pinning the display resolution, or diagnosing moire and
cropped/misaligned game viewports inside bezels. Covers why a shaderset seems
invalid, why .info top values are not alpha coordinates, and why Mega-Bezel
per-game art fails. Not for performance/framerate tuning (batocera-tuning).
compatibility: Requires SSH access to a Batocera host (tested on v41-v43).
---
# Batocera Display
Shaders, bezels, and display mode — everything about how the picture looks.
Assumes the connection pattern, `batocera.conf`/configgen model, and verify loop
from `batocera-ops`. Because display changes are visual, the
`batocera-ops` **remote verify loop** (ES HTTP API launch + `batocera-screenshot`)
is your primary tool here: never declare a shader or bezel "working" from the
config alone — launch it and look.
## Shaders
Shaders are assigned per-system (or per-game) in `batocera.conf`. Two key
mechanisms and the traps around each.
### `shaderset` resolves against TWO search paths
`<system>.shaderset=<name>` is a **folder-name lookup** resolved against both:
1. `/userdata/shaders/configs/<name>/` (user-installed)
2. `/usr/share/batocera/shaders/configs/<name>/` (system-bundled)
Checking only one path is enough to wrongly declare a preset invalid. Some
built-in names are aliases that resolve to a preset deep under
`/usr/share/batocera/shaders/`. Before concluding a `shaderset` value is dead:
```bash
SSHB "find /userdata/shaders /usr/share/batocera/shaders -maxdepth 3 -iname '<name>*'"
```
And read the launch log for the shader actually used on the last launch — it is
authoritative:
```bash
SSHB "grep -i 'set-shader\|shader' /userdata/system/logs/es_launch_stdout.log | tail"
```
### Two shader keys: `shaderset` vs `-renderer.shader`
- `<system>.shaderset=<name>` — a Batocera shaderset folder (may bundle bezel
art, e.g. Koko-AIO/Duimon presets).
- `<system>-renderer.shader=<path>` — a direct RetroArch `.slangp` path (relative
to the shaders dir, or absolute). This is how you assign a plain CRT preset
like `crt/crt-super-xbr` or a custom `.slangp`.
Per-game overrides use the exact-filename key from `batocera-ops` →
batocera-conf: `snes["Super Mario World (USA).sfc"]-renderer.shader=...`. A glob
(`snes["Star Fox*"]...`) **never matches**.
### Anti-moiré: integer scaling is impossible inside a custom-viewport bezel
A CRT mask shader (crt-royale and friends) can produce rainbow moiré when the
game isn't integer-scaled. The usual cure — `video_scale_integer` — **is ignored
for a custom viewport**, and bezels that force a custom RetroArch viewport (with
fractional coordinates) put you exactly there. So you cannot integer-scale inside
those bezels.
The workaround is to tame the mask instead of the scale: a wrapper `.slangp` that
`#reference`s the CRT preset and sets `mask_sample_mode_desired=1` (a
scale-tolerant mask sampling mode). Point the arcade systems' `-renderer.shader`
at the wrapper. Verify with before/after screenshots — this is precisely the kind
of change that must be seen, not assumed. Full detail in
[references/shaders.md](references/shaders.md).
### Pin the display mode in batocera.conf, not the boot conf
If a CEC-controlled TV renegotiates EDID on each wake and sometimes lands on
59.94Hz/1080i, pin it: `es.resolution=1920x1080.60.00`. This **must** go in
`/userdata/system/batocera.conf`, not `/boot/batocera-boot.conf` (the boot
conf's lower section is auto-synced from batocera.conf on boot and a direct edit
gets blanked). Confirm in `display.log` after a reboot.
## Bezels
Bezels/overlays are PNGs with a transparent "cutout" where the game shows,
plus a `.info` sidecar describing the viewport. Packs live under
`/userdata/decorations/<pack>/`, with per-game art at
`.../games/<system>/<rom>.png` (+ `.png.info`).
### `.info` coordinates are artistic intent, NOT the alpha cutout
The single biggest bezel trap. When a game renders misaligned inside the cutout,
the fix is the `.info` sidecar's `top`/`left`/`bottom`/`right` — but **`top` is
not the PNG's transparent-pixel offset.** It's where the artist intends the
game's viewport edge to sit, often *above* the alpha cutout because the top
bezel artwork is semi-transparent and the game is meant to extend behind it.
Do **not** derive `.info top` from the alpha bounding box (autodetecting the
transparent region and using that as `top` moves the game the wrong way).
Instead: launch, screenshot, and nudge `top` in ~20px steps (smaller = game
moves up, larger = down). Back up the `.info` before each iteration. See
[references/bezels.md](references/bezels.md).
### Generating missing `.info` sidecars
Some packs (notably thebezelproject) ship thousands of PNGs **without** `.info`
files. Without a sidecar, configgen guesses a viewport heuristically and often
gets it wrong (undersized, top-anchored game). [scripts/generate_tbp_info.py](scripts/generate_tbp_info.py)
alpha-detects each PNG's cutout and writes a correct `.info`, never overwriting
an existing one. This is the correct use of alpha detection — sizing a *missing*
sidecar — as opposed to the misalignment fix above, which is artistic and
eyeballed.
### Filtering unusable bezels
Some bezels have a cutout so small the game is postage-stamped. [scripts/filter_small_bezels.py](scripts/filter_small_bezels.py)
moves any bezel whose cutout's shorter dimension is below a threshold into a
`filtered_too_small/` sidecar dir, so Batocera falls back to the pack default.
Fully reversible (`mv filtered_too_small/* .`).
### SNES (and similar) fallback strategy
When a pack has per-game art for only a fraction of a large library (e.g. ~50 of
~760 SNES ROMs) and everything else falls back to an ugly generic system bezel,
invert the default: set the system to a clean shader-frame look (a Koko-AIO
ambilight preset, `<system>.bezel=none`) and write **per-game overrides** only
for the ROMs that *do* have real art. [scripts/snes-bezel-fallback.py](scripts/snes-bezel-fallback.py)
is a ready-to-adapt implementation (idempotent, generates a marker block,
backs up `batocera.conf`).
### Don't attempt per-game Mega-Bezel cabinet art
Combining Mega-Bezel's photoreal CRT with per-game cabinet-art bezels is a known
dead end on Batocera (stale bezel symlink returns `bezel: None`, rotation isn't
propagated to the shader, and per-game cutout variance can't be bracketed by one
global scale offset). The full postmortem — including the false-positive that
fools you into thinking it works after one ROM — is in
[references/bezels.md](references/bezels.md). Read it before spending a day on
this; the short version is *don't*.
Files in this skill
- SKILL.md
- references/bezels.md
- references/shaders.md
- scripts/filter_small_bezels.py
- scripts/generate_tbp_info.py
- scripts/snes-bezel-fallback.py
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.