Skip to content
dtcristoPublic

About

Engine for retro 2.5D environments

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

sector

Retro 2.5D sector/portal renderer built with Rust and Bevy

Overview

sector is an experimental software-rendered engine for Doom-style 2.5D environments. It uses convex sectors, explicit portals, flat floor/ceiling planes, optional open ceilings with either black fallback or flat sky tint, and per-surface flat colors to produce a crisp retro look with banded shading and single-pixel seams.

The native runtime and editor share the same SectorMap data model across RON and Protobuf assets. Shipped maps use Protobuf in assets/maps/*.map.pb; the editor also supports RON for authoring. The runtime treats 4:3 as the baseline view but adapts its logical render buffer to the current window, widening out to 32:9 or growing vertically to 9:21 before letterboxing extreme shapes. The web build ships the play runtime and serves each map as a separate static asset selected from the URL path.

Repository docs

  • DESIGN.md: current architecture, renderer/runtime design, map format, and system constraints
  • TODO.md: future improvements and follow-up work

Repository layout

  • src/bin/sector/main.rs: runtime entry point
  • src/bin/sector_edit/main.rs: egui-based editor entry point
  • src/bin/sector_import_doom/main.rs: DOOM WAD to sector-map importer
  • src/bin/sector_validate/main.rs: standalone map validator
  • src/game/: player input and physics
  • src/render/: software renderer, automap, and projection math
  • src/map.rs: map asset loading, saving, and validation
  • src/world.rs: runtime sector and wall data
  • assets/maps/: shipped map assets

Common commands

cargo test --features "sector sector_edit doom_import"
cargo run --features sector --bin sector -- assets/maps/default.map.pb
cargo run --features sector --bin sector -- assets/maps/e1m1.map.pb
cargo run --features sector_edit --bin sector_edit
cargo run --bin sector_import_doom --features doom_import -- ../DOOM1.WAD E1M1
cargo run --bin sector_validate -- assets/maps/default.map.pb

If you use just, the current shortcuts are:

just play
just play default
just play e1m1
just edit
just build-web
just serve-web
just import-doom ../DOOM1.WAD E1M1
just validate
just validate default
just validate e1m1

just play and just validate both default to the default map when no map name is provided.

While playing, press Shift+/ (?) to print a RON-style runtime state dump to the console with the player's position, velocity, facing, resolved sector, movement flags, and current sector wall/portal data. Press F3 to toggle rolling renderer stage timings in the console, or launch native play with SECTOR_RENDER_TIMINGS=1 to start with those timings enabled. Left click captures the cursor for play, right click or Escape releases it, N toggles noclip, and a double tap on Space toggles fly mode. While flying, hold Space to rise and Ctrl to descend; clipping still applies unless noclip is also enabled. Movement simulation now runs on a fixed Bevy timestep for steadier behavior across frame rates. As the window changes size, the game resizes its live pixel buffer and FOV together instead of staying locked to a single 320x240 view; 4:3 remains the preferred baseline, while wider windows reveal more horizontally and taller windows reveal more vertically. Native play now uses no-vsync presentation so frame diagnostics and renderer timings expose the real runtime cost instead of idling on a 60 Hz swap cap.

Editor

sector_edit is now a native-only egui map authoring tool over the shared map format. It can:

  • create a new starter map, open/reload existing maps, and save or save-as both .map.ron and .map.pb files
  • validate the current document on save before it writes anything
  • edit sector heights, wall colors, trims, portal walkability, spawn position, and spawn facing
  • center the map view around the spawn on load and give you explicit pan/zoom controls while editing
  • draft new rooms directly in the map view, splitting concave outlines into convex sectors automatically
  • rebuild portals by matching reversed wall edges across adjacent sectors
  • launch the runtime beside the editor so you can save and replay the current map quickly

Web build

just build-web builds the browser version of the play runtime only. The editor and Doom importer are native-only tools. Install the wasm target and the CLI version matching the pinned wasm-bindgen dependency first:

rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.127 --locked

just serve-web serves the wasm/ directory as a small SPA so map routes work locally. The browser runtime resolves the map from the URL path:

  • / or /default loads the default map
  • /e1m1 loads E1M1

The web build records shipped map names and paths at build time without embedding their contents in Wasm. The runtime fetches the selected .map.pb from assets/maps/ after startup, so maps remain independent static assets. Add a shipped Protobuf map and rebuild to expose it at /<map-name>.

The browser build uses the same adaptive viewport rules as native play, so route-selected maps keep the same 4:3 baseline feel while still making better use of wide and tall windows. The canvas follows the browser viewport even below the native 320x240 minimum, and the GPU surface catches up with any resize during startup. Native and web builds use published bevy_pixels 0.17 with Bevy 0.19. No sibling checkout is needed. Protobuf maps use the same .map.pb schema on both platforms through the pure Rust prost runtime.

CI/CD

.github/workflows/ci-cd.yml runs formatting, native checks, tests, shipped-map validation, and the web bundle build on pushes and pull requests. Pushes to main deploy the generated wasm/ bundle to Cloudflare Pages project sector.

Cloudflare Pages limits each static asset to 25 MiB. The deploy job Brotli-compresses the Wasm runtime and each shipped .map.pb file in place, then sets Content-Encoding: br while preserving their URLs. It also serves Wasm with application/wasm; browsers decode all Brotli assets automatically.

Keep the GitHub CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN Actions secrets configured. The token needs Account > Cloudflare Pages > Edit permission for the project's account. No Cloudflare dashboard changes are needed for this compression.

Shipped maps

  • default: bright 141-sector exploration demo. An angled foyer opens into a tall atrium with nested, stacked chambers. A two-turn spiral rises 9.6 metres to an overlook; a midway gallery and long descent loop back to the entrance. A nine-sector star court, sky courtyard, and crouch shortcut add alternate routes.
  • e1m1: imported from the DOOM shareware WAD as Protobuf, with door sectors held open from Doom door specials, zero-height door sectors, and door-texture heuristics while keeping the actual doorways walkable, sky sectors converted into no_ceiling spaces tinted from the map sky texture, spawn/facing matched to the Doom start, and wall/floor/ceiling colors derived from the average colors of the source textures and flats

Rebuild the default map with cargo run --bin sector_demo, then run just validate default. The generator authors convex pieces and joins shared boundaries into reciprocal portals. The rooms use the current engine geometry, including vertical overlap and view-only windows.

DOOM import workflow

sector_import_doom imports a WAD map by lump name and writes assets/maps/<map-id-lowercase>.map.pb by default:

cargo run --bin sector_import_doom --features doom_import -- ../DOOM1.WAD E1M1
just import-doom ../DOOM1.WAD E1M1

The importer:

  • preserves the Doom player start position and facing direction relative to the current player model
  • decomposes Doom sectors into convex cells that satisfy this engine's validation rules
  • converts F_SKY1 ceilings into no_ceiling sectors, tinting them from the correct Doom sky texture for the imported map
  • opens Doom door sectors from linedef specials, zero-height sectors, and door-texture hints, then clears those doorway portals so imported doors stay traversable
  • keeps non-door impassable linedefs as view-only portals
  • averages wall textures and flats into this engine's flat-color material model

Pass a third argument to the binary if you want a different output path.

Map authoring notes

  • Map coordinates are in meters.
  • Spawn position and facing direction live in the map asset.
  • Floor and ceiling planes can carry their own flat colors through floor_color and ceil_color.
  • Sectors must wind clockwise and remain convex.
  • Set no_ceiling: true on a sector to leave its ceiling open while still keeping its collision ceiling height. Leave sky_color unset for black sky, or set it to a flat tint for imported/open-sky sectors.
  • Portal walls can be marked walkable: false to create windows or skybox openings that render through to another sector but block traversal.
  • Portals must be reciprocal, agree on walkability, and provide real vertical openings.
  • Flat wall, floor, and ceiling colors are the current material system; there is no texture support yet.

Run map validation after map changes to catch winding, overlap, portal, and spawn issues early.

Current scope

The project currently focuses on:

  • fast headless validation and rendering tests
  • portal-based software rendering
  • simple first-person movement with stepping, jumping, crouching, and modest crouch-jumps
  • low-level map experimentation

The editor now supports real file workflows and map drafting, but runtime/rendering quality and performance are still the main priorities.

Credits

This project would not be possible without educational material and inspiration from:

License

Licensed under either of:

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.

Phone controls

Touch controls have no buttons. Your first finger chooses a role: starting on the left controls movement; starting on the right controls looking. A second finger takes the other role anywhere on screen, even on the same half. Roles stay fixed until each finger lifts. For movement, drag up/down to walk forward/backward and left/right to strafe. Keep holding to move, release to stop. For looking, swipe horizontally to look, or double-tap to jump. Double-tap with the movement finger and hold the second tap to crouch; drag that held finger to walk while crouched. Release to stand when headroom allows. A quick stationary two-finger tap anywhere cycles the automap: Off → Relative → Absolute → Off. Both thumbs work together in portrait and landscape. Touch jumping never toggles flight.

A short hint appears on touch devices and disappears after your first interaction. Turning the phone or leaving the page clears held gestures. Desktop keyboard and mouse controls remain available.

Run the fast gesture regression tests with node --test tests/touch.test.mjs.

Gamepad controls

Native and browser play support gamepads without mouse capture. Press a controller button or move a stick to start. Left stick walks and strafes with analog speed; right stick turns horizontally. A jumps, B holds crouch, left-stick click toggles crouch, and Select cycles the automap. Jump never toggles flight.

Stickless controllers use D-pad up/down to walk, left/right to turn, and L/R bumpers to strafe. Press Y to swap the horizontal bindings: D-pad left/right strafes and bumpers turn. Up/down and the sticks keep their original roles. Y toggles once per press and the selected layout lasts until Y is pressed again or the runtime restarts.

Native input uses platform controller mappings. Browsers support standard-mapped controllers and a common unmapped eight-button USB SNES profile: Y, B, A, X, L, R, Select, Start at button indices 0 through 7, with D-pad axes 0 and 1. USB adapters with other raw layouts need a matching profile. Release controls after returning from another window; focus loss and disconnect clear held movement, crouch and queued actions.

Run browser input tests with node --test tests/touch.test.mjs tests/gamepad.test.mjs.

About

Engine for retro 2.5D environments

Resources

Stars

0 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages