- Change only what the user asks for; do not touch unrelated user edits.
- No side refactors/cleanups beyond the requested scope.
- If a change spans multiple areas of a file, briefly state what will be altered before editing.
- Prefer small, precise patches over sweeping rewrites.
- Keep the project’s style/structure; do not impose your own organization unasked.
- Stop generating fucking bloat code.
- Do not cast without a reason.
- If a cast is truly necessary, use the shortest correct idiom (
std::move, a compactstatic_cast, or a tiny typed helper) instead of bloated conversion scaffolding. - Do not replace one bad cast style with another mechanically.
- Keep code compact; do not expand one-line logic into multiple temporaries unless it materially improves correctness or readability.
- If a type already has sane defaults, set only the fields that differ; do not bloat config objects with redundant
0/nullptr/empty/default assignments. - If a knob is expected to be changed from build
DEFINES, it must not be implemented asstatic constexpror a file-local tuning constant. Use#ifndef/#defineso the build can actually override it. - Do not bury tunable thresholds/timings/cutoffs inside
.cpplocals when they are likely to be adjusted during hardware tuning. Keep them overrideable from the build. - Do not hardcode per-type gameplay differences with
if (kind == ...)helper logic. Use data-driven archetypes/config passed into the instance. - Keep navigation/pathfinding outside actor classes. Actors may call a navigation module, but path logic itself does not belong inside the actor implementation.
- When a feature may later be loaded from data files, keep the runtime interface data-driven now so content additions do not require class code changes.
- Follow CODING_STANDARDS.md for any C++/Arduino code changes.
- Treat
CODING_STANDARDS.mdas mandatory, not advisory. - Keep Wolf-specific defaults and overrides in the Wolf project, not in shared
vendor/SGFprofiles or APIs. - Shared/library code must stay generic; do not introduce
WOLF_*behavior into SGF. - When touching shared SGF-facing components, consult vendor/SGF/README.md for the intended structure.
- For UI/text tweaks, do not alter wording unless necessary.
- When a change affects rendering/perf, verify whether the bottleneck is CPU render, SPI present, static DRAM, or runtime heap before claiming a cause.
- Ask when something is unclear instead of guessing.
- No false statements and no guessing: if unsure, inspect files and verify; double-check before claiming compliance.
- Preferred commands:
./vendor/SGF/tools/sgf build board=esp32./vendor/SGF/tools/sgf flash board=esp32./vendor/SGF/tools/sgf upload board=esp32./vendor/SGF/tools/sgf elfinfo -q board=esp32
sgfsemantics:build= compile onlyupload= upload existing build artifacts onlyflash= build + uploadelfinfo= inspect existing ELF unless--buildis given
- Useful flags:
-qquiet-vraw verbose output fromarduino-cli--clean-buildforces clean rebuild
- Common recovery for broken Arduino build artifacts:
rm -rf build/esp32 build-stage/esp32/wolf3d- then
./vendor/SGF/tools/sgf build --clean-build -q board=esp32
- Rendering defaults live in Game.h:
WOLF_VIEWPORT_W=240WOLF_VIEWPORT_H=180WOLF_SIMPLE_FLOOR=1WOLF_SIDE_SHADE=0WOLF_UPSCALE=2WOLF_UPSCALE_BUFFER_COUNT=2WOLF_DYNAMIC_FRAMEBUFFER=1WOLF_DYNAMIC_FRAMEBUFFER_ALIGNMENT=32WOLF_HEAP_COLD_BUFFERS=1
- Wolf project locally forces DMA bus in wolf3d.ino, not in SGF hardware presets.
WOLF_UPSCALE_BUFFER_COUNT=2is the measured sweet spot.4gave no gain and wastes SDRAM.- Dynamic framebuffer is acceptable only with aligned heap allocation; unaligned heap framebuffer caused large FPS regressions.
- Static DRAM bottleneck on ESP32 is
dram0_0_seg, not the full board RAM figure.elfinfoshows both. - For
UPSCALE=1, a static framebuffer nearly fillsdram0_0_seg; the dynamic aligned framebuffer avoids that. - Cold buffers moved to heap:
- HUD cache
- map data
- door open amounts
- BMP textures are not stored in static DRAM; they live in flash blob data and are lazily decoded to heap.
- Texture asset handling must stay outside
Game; use: - Build-time generators live in shared SGF tools:
sgf buildruns asset prebuild automatically whentextures/and/orsamples/are present.- Palette source of truth:
- textures/wolf-128.gpl
- generator reads
.gpl; it is not just an export artifact
- Generated assets:
- Current texture blob format is flash-backed and lazy-decoded by
Textures.
- For
8-bit indexed BMP, the generator preserves source indices1:1. - For
24/32-bit BMP, the generator quantizes by RGB to the project palette. - Sprite/weapon transparency rule:
- index
0is transparent - this is handled in
Textures, not by color-keying in game code
- index
- Do not reintroduce transparency checks based on
RGB565 == 0for sprite/weapon assets. - For non-sprite cases such as animated doors, treat “shifted out of texture” as a separate opaque/non-opaque state; do not use color
0as transparency.
- Full manifest is in textures.txt.
- Important symbols:
- walls:
# A B C E F G H I - doors:
D 1 2 3 - keys:
r g b - pickups:
aammo,hmedkit - zombie:
@
- walls:
- Door asset names:
door_plaindoor_reddoor_greendoor_blue
- Sprite asset names:
sprite_key_redsprite_key_greensprite_key_bluesprite_ammosprite_medkitsprite_zombie
- Weapon asset name:
weapon_pistol
- Sprite rendering is centralized:
- Sprite.h
- SpriteRenderer.h
renderSprites(), not separaterenderKeys()/renderZombies()
Gameshould orchestrate; asset/render specifics belong in dedicated modules.- Walls and doors use dedicated modules:
- Doors are animated via
doorOpenAmounts, not binary open/closed only. - Colored doors keep their visual type after first unlock; unlock state is stored separately from the map tile.
- Door raycasting has an alpha-scissor-like behavior:
- if a shifted door column is outside the texture, DDA continues and can render the wall behind the door.
- Player starts with
10ammo. - Zombie HP and damage logic are not instant-kill:
- zombies have HP
- shot damage has light randomness and distance falloff