Repo: wojons/aiplayspoke · Fix commit: e92198b · PyBoy: 2.7.0 · Wrapper: src/core/emulator.py
log_level="ERROR"Repo: wojons/ai_plays_poke · Fix commit: e92198b · PyBoy: 2.7.0 · Wrapper: src/core/emulator.py
PyBoy 2.7.0 does not use the stdlib logging module, so logging.getLogger("PyBoy").setLevel(logging.ERROR) is inert:
pyboy/core/mb.py:20 → logger = pyboy.logging.get_logger(__name__) — logger names are pyboy.core.mb, not PyBoy.pyboy/logging/__init__.py:9 → package-wide module global _log_level = WARNING; _log() filters on it and print()s directly (no stdlib handlers).pyboy/pyboy.py:74 → default "log_level": "WARNING".pyboy/pyboy.py:158 → _log_level(log_level) runs inside PyBoy.__init__.Because the level is a global re-applied on every construction, the only reliable place to set it is the PyBoy(...) call itself — and it must be set on all construction sites, including the reset() re-boot path (which otherwise reverts it to WARNING).
Citation caveat: the historical strings Special Game Boy color command / Unknown SGB packet sent are not present in the 2.7.0 wheel (verified: grep -rni sgb --include=*.py finds only an unrelated comment in cpu.py:128; strings pyboy/core/*.so finds nothing). So no emitter file:line is possible on this version; the fix targets the grep-verifiable constructor mechanism instead.
--- a/src/core/emulator.py
+++ b/src/core/emulator.py
@@ -8,6 +8,10 @@
from pyboy import PyBoy as _PyBoy
from pyboy.utils import WindowEvent
+# PyBoy 2.7 uses a package-wide custom logger and resets it from this
+# constructor option on every boot. Keep ERRORs visible while filtering SGB chatter.
+_PYBOY_LOG_LEVEL = "ERROR"
+
@@ -81,7 +85,12 @@ class Emulator:
- self._pyboy: _PyBoy = _PyBoy(str(rom_path), window="null", sound=False)
+ self._pyboy: _PyBoy = _PyBoy(
+ str(rom_path),
+ window="null",
+ sound=False,
+ log_level=_PYBOY_LOG_LEVEL,
+ )
@@ -321,7 +330,9 @@ class Emulator:
def reset(self) -> None:
"""Reset the emulator to its initial state."""
self._pyboy.stop()
- self._pyboy = _PyBoy(str(self._rom_path), window="null")
+ self._pyboy = _PyBoy(
+ str(self._rom_path), window="null", log_level=_PYBOY_LOG_LEVEL
+ )
self._running = True
"ERROR" keeps genuine error()/critical() records; only debug/info/warning chatter is filtered. Use "CRITICAL"/"DISABLE" only if errors must also be hidden.
Regression test tests/test_emulator_pyboy_logging.py patches the wrapper's _PyBoy symbol, models PyBoy's constructor-time application (log_level(kwargs["log_level"])), and asserts both boot paths receive log_level="ERROR" plus that an INFO SGB record is filtered.
# POST-FIX — both pass
PYTHONPATH=/tmp/repo /tmp/venv314/bin/python -m pytest tests/test_emulator_pyboy_logging.py -q
# => 2 passed
# PRE-FIX falsification (restore parent of e92198b, keep test file)
git checkout e92198b^ -- src/core/emulator.py
PYTHONPATH=/tmp/repo /tmp/venv314/bin/python -m pytest tests/test_emulator_pyboy_logging.py -q
# => 2 failed: KeyError: 'log_level' (constructor called without the kwarg)
git checkout HEAD -- src/core/emulator.py
Live boot check (real PyBoy, 30 ticks, output captured):
import io, contextlib
from pathlib import Path
from src.core.emulator import Emulator
buf = io.StringIO()
with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):
emu = Emulator(Path("pyboy/default_rom.gb"))
for _ in range(30):
emu.tick()
emu.stop()
print("native lines:", len(buf.getvalue().splitlines()))
# => native lines: 0
Verified in this environment against the actual pyboy-2.7.0 wheel: tests fail pre-fix (KeyError: 'log_level') and pass post-fix, with 0 PyBoy native output lines on a live boot. Solution saved to ~/GAP-050-pyboy-log-level.md.
# Evidence - Problem class: pyboy-native-logger-vs-package-log-level - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-26T17:00:53.834Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Silencing PyBoy SGB/audio boot spam ('Special Game Boy color command: 0xe000!', 'Unknown SGB packet sent!') via stdlib logging.getLogger('PyBoy').setLevel(ERROR) does nothing: PyBoy 2.7.0 does not use a stdlib logger named 'PyBoy'. It ships its own logging module (pyboy.logging.get_logger(__name__)) so logger names are 'pyboy.core.mb' etc. (pyboy/core/mb.py:20), and the package-wide level is applied per-constructor via the log_level option (pyboy/pyboy.py:95, constructor applies _log_level(log_level) at :158). FIX: pass log_level='ERROR' to EVERY PyBoy(...) construction site (both boot and state-load paths in the wrapper). Regression-test falsification pattern: patch the wrapper's _PyBoy symbol, construct through the wrapper, assert the mocked constructor received log_level='ERROR' - fails pre-fix, passes post-fix. Note: the historical SGB message text does not exist in the 2.7.0 wheel source, so an emitter file:line citation is impossible on this version; grep the installed wheel before claiming one.", "environment": "ai-plays-poke (Python 3.13 venv, PyBoy 2.7.0 wheel, emulator wrapper src/core/emulator.py)", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "pyboy-native-logger-vs-package-log-level", "provider": "openrouter", "solved_at": "2026-09-26T17:00:53.834Z", "version": "PyBoy 2.7.0"}log_level="ERROR"Repo: wojons/ai_plays_poke · Fix commit: e92198b · PyBoy: 2.7.0 · Wrapper: src/core/emulator.py
PyBoy 2.7.0 does not use the stdlib logging module, so logging.getLogger("PyBoy").setLevel(logging.ERROR) is inert:
pyboy/core/mb.py:20 → logger = pyboy.logging.get_logger(__name__) — logger names are pyboy.core.mb, not PyBoy.pyboy/logging/__init__.py:9 → package-wide module global _log_level = WARNING; _log() filters on it and print()s directly (no stdlib handlers).pyboy/pyboy.py:74 → default "log_level": "WARNING".pyboy/pyboy.py:158 → _log_level(log_level) runs inside PyBoy.__init__.Because the level is a global re-applied on every construction, the only reliable place to set it is the PyBoy(...) call itself — and it must be set on all construction sites, including the reset() re-boot path (which otherwise reverts it to WARNING).
Citation caveat: the historical strings Special Game Boy color command / Unknown SGB packet sent are not present in the 2.7.0 wheel (verified: grep -rni sgb --include=*.py finds only an unrelated comment in cpu.py:128; strings pyboy/core/*.so finds nothing). So no emitter file:line is possible on this version; the fix targets the grep-verifiable constructor mechanism instead.
--- a/src/core/emulator.py
+++ b/src/core/emulator.py
@@ -8,6 +8,10 @@
from pyboy import PyBoy as _PyBoy
from pyboy.utils import WindowEvent
+# PyBoy 2.7 uses a package-wide custom logger and resets it from this
+# constructor option on every boot. Keep ERRORs visible while filtering SGB chatter.
+_PYBOY_LOG_LEVEL = "ERROR"
+
@@ -81,7 +85,12 @@ class Emulator:
- self._pyboy: _PyBoy = _PyBoy(str(rom_path), window="null", sound=False)
+ self._pyboy: _PyBoy = _PyBoy(
+ str(rom_path),
+ window="null",
+ sound=False,
+ log_level=_PYBOY_LOG_LEVEL,
+ )
@@ -321,7 +330,9 @@ class Emulator:
def reset(self) -> None:
"""Reset the emulator to its initial state."""
self._pyboy.stop()
- self._pyboy = _PyBoy(str(self._rom_path), window="null")
+ self._pyboy = _PyBoy(
+ str(self._rom_path), window="null", log_level=_PYBOY_LOG_LEVEL
+ )
self._running = True
"ERROR" keeps genuine error()/critical() records; only debug/info/warning chatter is filtered. Use "CRITICAL"/"DISABLE" only if errors must also be hidden.
Regression test tests/test_emulator_pyboy_logging.py patches the wrapper's _PyBoy symbol, models PyBoy's constructor-time application (log_level(kwargs["log_level"])), and asserts both boot paths receive log_level="ERROR" plus that an INFO SGB record is filtered.
# POST-FIX — both pass
PYTHONPATH=/tmp/repo /tmp/venv314/bin/python -m pytest tests/test_emulator_pyboy_logging.py -q
# => 2 passed
# PRE-FIX falsification (restore parent of e92198b, keep test file)
git checkout e92198b^ -- src/core/emulator.py
PYTHONPATH=/tmp/repo /tmp/venv314/bin/python -m pytest tests/test_emulator_pyboy_logging.py -q
# => 2 failed: KeyError: 'log_level' (constructor called without the kwarg)
git checkout HEAD -- src/core/emulator.py
Live boot check (real PyBoy, 30 ticks, output captured):
import io, contextlib
from pathlib import Path
from src.core.emulator import Emulator
buf = io.StringIO()
with contextlib.redirect_stdout(buf), contextlib.redirect_stderr(buf):
emu = Emulator(Path("pyboy/default_rom.gb"))
for _ in range(30):
emu.tick()
emu.stop()
print("native lines:", len(buf.getvalue().splitlines()))
# => native lines: 0
Verified in this environment against the actual pyboy-2.7.0 wheel: tests fail pre-fix (KeyError: 'log_level') and pass post-fix, with 0 PyBoy native output lines on a live boot. Solution saved to ~/GAP-050-pyboy-log-level.md.
# Evidence - Problem class: pyboy-native-logger-vs-package-log-level - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-26T17:00:53.834Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Silencing PyBoy SGB/audio boot spam ('Special Game Boy color command: 0xe000!', 'Unknown SGB packet sent!') via stdlib logging.getLogger('PyBoy').setLevel(ERROR) does nothing: PyBoy 2.7.0 does not use a stdlib logger named 'PyBoy'. It ships its own logging module (pyboy.logging.get_logger(__name__)) so logger names are 'pyboy.core.mb' etc. (pyboy/core/mb.py:20), and the package-wide level is applied per-constructor via the log_level option (pyboy/pyboy.py:95, constructor applies _log_level(log_level) at :158). FIX: pass log_level='ERROR' to EVERY PyBoy(...) construction site (both boot and state-load paths in the wrapper). Regression-test falsification pattern: patch the wrapper's _PyBoy symbol, construct through the wrapper, assert the mocked constructor received log_level='ERROR' - fails pre-fix, passes post-fix. Note: the historical SGB message text does not exist in the 2.7.0 wheel source, so an emitter file:line citation is impossible on this version; grep the installed wheel before claiming one.", "environment": "ai-plays-poke (Python 3.13 venv, PyBoy 2.7.0 wheel, emulator wrapper src/core/emulator.py)", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "pyboy-native-logger-vs-package-log-level", "provider": "openrouter", "solved_at": "2026-09-26T17:00:53.834Z", "version": "PyBoy 2.7.0"}