Skip to main content
The rewrite uses a clean two-package architecture under src/:
  • src/grim/ — Engine/platform layer (raylib wrapper, assets, rendering helpers)
  • src/crimson/ — Game layer (modes, simulation, UI, persistence, data tables)
This mirrors the original grim.dll vs crimsonland.exe boundary while staying idiomatic for Python.

Design Principles

Import Rule: grim must not import crimson. The game layer can import engine helpers, but not vice versa.
  1. grim should not import crimson
  2. crimson can import grim helpers
  3. Keep raylib calls confined to grim as much as practical
  4. Prefer small moves that preserve working entrypoints
  5. No compatibility re-exports or stubs when moving modules

Grim Module (Engine Layer)

Location: src/grim/

Core Subsystems

ModulePurposeKey Files
Window/LoopWindow init, main loop, FPS, view protocolapp.py, view.py
AssetsPAQ/JAZ decoders, texture cachepaq.py, jaz.py, assets.py
InputKeyboard/mouse/gamepad wrapperinput.py
RenderingTerrain renderer, geometry helpersterrain_render.py, geom.py, color.py
AudioMusic/SFX playbackaudio.py, music.py, sfx.py, sfx_map.py
Configcrimson.cfg persistenceconfig.py
ConsoleDebug console overlayconsole.py
FontsFont loaders and text renderingfonts/small.py, fonts/grim_mono.py
MathGeometry primitivesmath.py, geom.py
RandomRNG wrapperrand.py

Example: Terrain Rendering

src/grim/terrain_render.py
from grim.raylib_api import rl

class GroundRenderer:
    """Pure render pipeline for terrain.
    Game logic selects params, this draws."""
    
    def render_to_target(
        self,
        *,
        terrain_id: int,
        scroll_u: float,
        scroll_v: float,
        width: int,
        height: int,
    ) -> rl.RenderTexture:
        # Render terrain with UV scrolling
        ...

Crimson Module (Game Layer)

Location: src/crimson/

Core Subsystems

ModulePurposeKey Files
CLICommand-line interfacecli/root.py, cli/replay.py
Game FlowState machine, boot sequencegame/__init__.py, demo.py
ModesGameplay mode implementationsmodes/survival_mode.py, modes/rush_mode.py, etc.
SimulationDeterministic world statesim/world_state.py, sim/step_pipeline.py
GameplayPlayer update, weapon handlinggameplay.py, weapon_runtime.py
CreaturesAI, animations, spawningcreatures/ai.py, creatures/runtime.py
ProjectilesProjectile pools and updatesprojectiles/runtime.py, projectiles/types.py
BonusesPickup logic and effectsbonuses/apply.py, bonuses/pool.py
PerksPerk runtime architectureperks/runtime/manifest.py, perks/impl/*
QuestsQuest builders (tiers 1-5)quests/tier1.py, quests/runtime.py
TutorialTutorial stage progressiontutorial/stages.py, tutorial/hints.py
Typ-oTyping mechanicstypo/target_matching.py
NetworkingRollback netplay, lockstepnet/rollback.py, net/lan_lockstep.py
ReplayDeterministic replay systemreplay/codec.py, replay/recorder.py
UIHUD, menus, panelsui/hud.py, frontend/menu.py
PersistenceSave files, high scorespersistence/save_status.py
Audio RoutingGameplay audio eventsaudio_router.py, weapon_sfx.py
Data TablesWeapons, perks, creaturesweapons.py, perks/ids.py

Example: Game Mode Structure

src/crimson/modes/survival_mode.py
from grim.view import View
from crimson.game_world import GameWorld
from crimson.sim.sessions import SurvivalDeterministicSession

class SurvivalMode:
    """Survival game mode implementation."""
    
    def __init__(self, world: GameWorld):
        self.world = world
        self.session = SurvivalDeterministicSession(
            world=world.world_state,
            options=...,
        )
    
    def update(self, dt: float) -> None:
        # Run deterministic step
        result = self.session.step_tick(
            dt=dt,
            inputs=self.collect_inputs(),
        )
        # Apply presentation
        self.world.apply_presentation(result)

Module Organization

Simulation Layer

The deterministic simulation core lives in src/crimson/sim/:
sim/
  world_state.py          # Core world state container
  step_pipeline.py        # Deterministic tick contract
  sessions.py             # Mode-specific session adapters
  input_frame.py          # Multiplayer input normalization
  presentation_step.py    # Deterministic presentation planning
  timing.py               # Frame timing helpers
  driver/                 # Replay runners
    replay_runner.py      # Headless verification
    replay_info.py        # Timeline extraction

Perks Architecture

Perks are split into three layers (see Perks Module):
perks/
  ids.py                  # Perk IDs and metadata
  availability.py         # Selection logic
  state.py                # Runtime state
  impl/                   # One file per perk
    instant_winner.py
    final_revenge.py
    ...
  runtime/                # Dispatch orchestration
    manifest.py           # Canonical registry
    hook_types.py         # Hook contracts
    apply.py              # Apply-time dispatcher
    effects.py            # Frame effects dispatcher
No circular imports: impl and runtime must not import availability or selection. Keep registration centralized in runtime/manifest.py.

Prefix Map (Decompile → Rewrite)

Original binary prefix clusters map to rewrite packages:
Original PrefixRewrite Package
grim_*grim.* (graphics/input/audio/assets)
resource_*, buffer_reader_*grim.paq, grim.assets
console_*grim.console
ui_*, hud_*crimson.ui.*
quest_*, survival_*, rush_*crimson.modes.*
player_*, creature_*, projectile_*crimson.sim.*, crimson.gameplay
weapon_*, perk_*crimson.weapons, crimson.perks.*
bonus_*, effect_*, fx_*crimson.bonuses.*, crimson.effects
highscore_*crimson.persistence.*

File Locations

Key entry points and their locations:
  • src/crimson/cli/root.py — Main CLI (crimson command)
  • src/crimson/game/__init__.py — Game flow state machine
  • src/crimson/demo.py — Demo/attract mode
  • src/crimson/sim/world_state.py — World step logic
  • src/crimson/sim/step_pipeline.py — Deterministic pipeline
  • src/crimson/sim/sessions.py — Mode session adapters
  • src/grim/terrain_render.py — Terrain rendering
  • src/crimson/render/world/ — World sprite rendering
  • src/crimson/ui/hud.py — HUD overlay
  • src/grim/paq.py — PAQ archive reader
  • src/grim/jaz.py — JAZ texture decoder
  • src/grim/assets.py — Texture cache

Import Boundaries

The project uses import-linter to enforce architectural boundaries:
# ✅ Good: Game imports engine
from grim.assets import TextureCache
from grim.raylib_api import rl

# ❌ Bad: Engine imports game
from crimson.gameplay import player_update  # Not allowed in grim/

# ✅ Good: Impl declares hooks
from crimson.perks.runtime.hook_types import PerkHooks

# ❌ Bad: Impl imports selection
from crimson.perks.availability import perks_rebuild_available  # Not allowed in impl/

Next Steps

Game Loop

Understand the frame pipeline

Deterministic Pipeline

Learn about the step contract

Crimson Module

Dive into game logic

Grim Module

Explore the engine layer

Build docs developers (and LLMs) love