Automated summary
Walks through adding a new game engine (backend) or Gymnasium adapter to the fmri-gym project, from adapter code to config, tests and CI wiring.
Strengths
- Ordered, numbered checklist covering adapter, config, pyproject extras, tests and CI matrix
- Clear separation between game logic (Gym env) and adapter, with existing files to imitate as models
- Treats replay reproducibility as the real acceptance criterion, not just playability
- Calls out concrete traps such as pygame key naming and game phases having no default key map
Limitations
- Highly specific to the fmri-gym repository and its internal conventions
- The provided text cuts off mid-sentence on action-space handling
- Assumes familiarity with Gymnasium, uv, pytest and GitHub Actions workflows
Reach for it when adding, porting or wiring a game into fmri-gym and you want to cover every required step.
Not useful outside fmri-gym or when no game environment needs integrating.
Security analysis
SafeThe skill is pure developer documentation for adding a game-engine backend: it describes file layout, hooks, config conventions and reproducibility pitfalls, and its only commands are local test/lint/play invocations (`pytest`, `uv run`, `ruff`). There is no destructive command, no obfuscated payload, no credential or token handling, and no instruction to disable safety checks. The single supply-chain surface is the pinned git dependency on a maintainer fork, which is disclosed in-line and used for a stated determinism reason rather than concealment.
- •Step 4 introduces a pinned third-party dependency installed directly from a GitHub fork (`crafter @ git+https://github.com/chengfanbrain/crafter.git@deterministic`); this is a legitimate determinism fix but pulls code from a non-upstream personal repository, so provenance should be confirmed before wide use.
Examples
Add a new backend for the game NetHack to fmri-gym: create the adapter, the curriculum config, the pyproject extra and the tests row, then run the backend test suite.Does an existing fmri-gym backend like ale or gym already play this ROM out of the box, or do I need to write a new adapter?My new backend passes playable and recorded checks but fails the replays test — what should I look at in the curriculum keys map?name: add-backend description: Add a new game engine (backend) to fmri-gym, or wrap a game that has no Gymnasium env: the adapter, the gym/ package, the config, the pyproject extra, the tests row, and the traps that make a backend look fine and replay wrong. Use when asked to add, port or wire up a game.
Adding a backend
Read AGENTS.md first: rule 1 (the game logic lives in the gym env, not the adapter) decides where every line below goes.
The checklist
- Check that no existing backend already plays the game, possibly out of the box:
aletakes any Atari ROM,retroany libretro core,gymany registered Gymnasium env,vizdoomany Doom scenario. If one does, all you need is a config. fmri_gym/adapters/<BACKEND>.py: module docstring (what the engine is, why it was chosen, what its observation/action spaces look like, what is not supported), then a subclass ofEnvAdapteroverriding only the hooks you actually need. Everything else is inherited; do not re-state the defaults.adapters/vizdoom.py,baba.pyandminihack.pyare the models; stay under about 150 lines, one file.fmri_gym/adapters/__init__.py: oneif backend == ...:branch with a lazy import.configs/curricula/<BACKEND>/<GAME>.json: a runnable curriculum, andconfigs/curricula/<BACKEND>/common/README.mdwith any install step that is not a plainpip installand what the action indices in itskeysmean. Comments in the JSON ("## ..."keys) are for what is particular to one file, and few. Lay it out like the others: one##comment per line, the message'stextone screen line per line, fixations as one-liners, and the game block on a few lines withkeys(andmenu) inline, not one key per line. A game with several levels gets one curriculum per level, whose game phases__includea sharedconfigs/curricula/<BACKEND>/common/<GAME>.json(the keys, the instructions, the settings) and state only the level and its title (README, "Shared phases").pyproject.toml: one extra under[project.optional-dependencies](and a mention indbp/allif it belongs there), thenuv lock. A game without a Gymnasium env gets one first, as its own small package undergym/<GAME>/(pyproject.toml,README.md,<game>_gym/{__init__,env}.py, agym.registerid), listed in[tool.uv.sources]; the extra installs that package. It wraps the game, it does not contain it.tests/backends.py: aBackendrow (the extra, the module that proves it is installed, one or two short smoke configs, thegym/dirs it runs on, itsexternal/checkout, acireason if a hosted Linux runner cannot play it), and a line in.github/workflows/tests.yml's matrix unless it has one.test_configsfails until the row exists.README.md: only if the backend needs setup beyondpip install(a repo checkout, a binary, an env var).
Then play it, through the suite and by hand:
uv run pytest --backend <BACKEND>
uv run fmri-play --subject sub-test --dummy-trigger --ses 1 --run 1 --rig configs/rigs/example/laptop.json \
--curriculum configs/curricula/<BACKEND>/<GAME>.json
uv run ruff check fmri_gym fmri_play.py agents tests
The suite's four checks (playable, recorded, replays, runs) are the acceptance test; the testing skill says how to read a failure. A backend that passes everything but replays is not done: reproducibility is the product.
Things that are easy to get wrong
- Key names are pygame names, upper-cased, without
K_("LEFT","SPACE","Z"). Add unlisted keys to_PYGAME_KEY_NAMESinkeys.pyrather than mapping keycodes yourself;validate_curriculumrefuses a config that names a key outside that table. - Every game phase writes its whole
keysmap; there is no default. AMultiBinaryenv takes button indices (held keys combine), anything else takes the action itself and a real-time phase also needs the""entry, the action for no key held. One class per action space inadapters/keymap.py. Don't add a per-backend default or a merge step: the map depends on the site's input device, not on the engine. - Copy observations you keep. Several envs reuse their observation buffers, so
capturemust.copy()anything it stores (seeminihack.py). The replay check is what found nethack logging each frame's variables one frame late: a view of a reused buffer, pickled after the loop had moved on. render()is not always free of side effects. An engine that draws from the same RNG its dynamics use spends a draw on every render, so one extra call shifts everything after it and the run silently stops matching its own log. Return the framestepalready produced rather than rendering again; crafter's night noise is the live example, and it is whygym/craftercaches the observation and hands that back.- Reproducibility is the product. A block must be replayable from
episode_seeds+actions. If the env has no savestate of its own, wrap it inreplay_state_gym.ReplayState(gym/replay_state/: the seed and the actions since, restored by replay) and make surereset(seed=...)really determines the episode. Test it rather than assume it:uv run python -m tests.verify_replay --curriculum <config>records a block with the random policy, replays it in a second process under anotherPYTHONHASHSEEDand compares every frame, every sound chunk and every savestate anchor bit for bit. A second process because the usual culprit is hash order, which is fixed for the life of one interpreter and would let a replay inside the recording process pass. Crafter passes every surface check (a seed argument, a seededRandomState, deterministic worldgen) and still diverges, because one creature list is built from a Pythonsetand the despawn pick therefore follows objectid(), which moves withPYTHONHASHSEED. Sorting that list is the whole fix, and it lives in a fork the env package pins (gym/crafter/pyproject.toml:crafter @ git+https://github.com/chengfanbrain/crafter.git@deterministic), not in the adapter: when the bug is in the engine, fix the engine and pin the build, or the rig starts owning game behaviour. - Sound is part of the record. An engine that makes sound returns this step's PCM from
sound()(the contract is inEnvAdapter.sound;retro.pyis a two-line example); the loop plays it and logs it, and the replay check compares it chunk for chunk. ViZDoom's first episode afterinit()sounded different from one afternew_episode(), which is whyvizdoom_gymbegins every episode the same way. - Turn-based games (grid worlds, puzzles) need
"is_turn_based": truein the config. - Slow real-time games (a grid world that must keep ticking, so
is_turn_basedis out) need"has_latched_keys": true: the default real-time path polls held keys, and at a few frames per second a tap that begins and ends between two frames is never seen.
From the file to a first result
The download contains exactly the content displayed on this page. This copy may differ from the latest version in the source repository.
View the source and supporting filesThis download contains SKILL.md only. Any scripts, references, templates and dependencies are not included. Get the required files from the source, preserving their directory structure, before continuing.
1. Download the file
Download SKILL.mdFrom the root of a test project, save the file as .skill-downloads/add-a-game-backend-to-fmri-gym-mvg6txds/SKILL.md, or use the command below. This folder is for inspecting the skill before installation.
Command for macOS/Linux or WSL: requires a POSIX shell and curl. On Windows without WSL, use the download button and create the folder manually.
mkdir -p '.skill-downloads/add-a-game-backend-to-fmri-gym-mvg6txds' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/add-a-game-backend-to-fmri-gym-mvg6txds/download' --output '.skill-downloads/add-a-game-backend-to-fmri-gym-mvg6txds/SKILL.md.part' && mv '.skill-downloads/add-a-game-backend-to-fmri-gym-mvg6txds/SKILL.md.part' '.skill-downloads/add-a-game-backend-to-fmri-gym-mvg6txds/SKILL.md'2. Check prerequisites
Read SKILL.md before using it. If it references scripts or external services, inspect them and prepare the required access in your test environment.
No recognized prerequisites section in this file. This does not mean it is self-contained: libraries, tools and access requirements may be mentioned elsewhere in the content or source repository.
The platforms on this page are catalog indications. The paths below follow tool documentation; they do not prove this skill has been run in those tools. No minimum tool version is confirmed for this listing.
3. Load the skill
This file does not provide a usable name/description header for this native procedure. It remains available to download and inspect; use the manual method below.
Manual method or another tool: attach SKILL.md to a conversation, or open it in an agent that can read files. Include any required supporting resources. No universal folder is recognized by every tool.
4. Try a first task
Use a short, synthetic input: an API endpoint, a small document or an interface brief. In a new conversation, attach the file or give the agent its path, then adapt this request:
Read .skill-downloads/add-a-game-backend-to-fmri-gym-mvg6txds/SKILL.md and apply its instructions to this example: [describe a test input]. Identify any missing files, dependencies or access. Produce the result requested by the skill without inventing data or running unreviewed scripts.Expected result: output matching the skill instructions, or a precise list of missing prerequisites. A read confirmation alone does not validate a first use. If an external resource is unavailable, that path remains untested.
React Hook Form Best Practices
Development
Master React Hook Form: avoid anti-patterns, manage subscriptions, and handle reset, dirty state, and controlled inputs correctly.
Emotion to Mantine Migration
Development
Migrate Emotion styled-components to Mantine components with style props and CSS modules. Use when converting .styled.tsx files or removing @emotion imports.
Scope Creep Detector
Development
Analyzes git diffs against a stated intent to detect scope creep, unrelated files, broad PRs, and oversized changes, then suggests keep, split, or justify actions.