Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ instead of colliding with a tower.
into the executable; a missing sprite falls back to the procedural block. The
shipped binary is still a single self-contained file per platform.

**Smaller fixes.** Frames pace to vsync (no tearing, no busy-wait), and the F3
overlay shows real frame time. Ore is visible on the first conveyor in short
**Smaller fixes.** Frames pace to vsync (no tearing, no busy-wait), and the debug
overlay (backtick) shows real frame time. Ore is visible on the first conveyor in short
layouts. World-space value labels scale with zoom instead of ballooning when zoomed
out. `./nob hot` stopped spamming a directory-exists line every half second.

Expand Down
50 changes: 40 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,39 @@ On Windows, SmartScreen will warn; click "More info" then "Run anyway".
## Playing with it

The game is a value loop: droppers spit out ore, conveyors route it, upgraders
raise its value toward a ceiling, and furnaces bank it. Ore climbs its
raise its value toward a ceiling, and furnaces bank it as money. Ore climbs its
ceiling a fraction per upgrader pass, and each *distinct* upgrader it meets
raises that ceiling. The puzzle is routing loops past as many different
upgraders as you can before cashing out.

Money is a balance you spend, not a score. You buy every piece you place, you buy
tiers of dropper and upgrader, and you buy the grid itself: the build area starts
at 4x4 and doubles for a price that scales with the space unlocked. The shop is
the right-hand panel. A loop needs a **splitter** to be worth building: every
other piece has a single facing, so ore that enters a loop rides it forever, and
the splitter is the tap that lets a full loop drain to the furnace.

| Input | Action |
|---|---|
| WASD / Arrows | Pan the camera |
| Scroll wheel | Zoom |
| 1 / 2 / 3 / 4 / 5 | Pick Dropper / Conveyor / Upgrader / Furnace / Delete (press again to drop it) |
| 1 / 2 / 3 / 4 / 5 / 6 | Pick Dropper / Conveyor / Upgrader / Furnace / Delete / Splitter (press again to drop it) |
| R | Rotate the piece under the cursor in place; over an empty tile, rotate the facing of the next piece placed |
| T | Turn a splitter's branch: which quarter-turn its second output takes |
| Left click | Place the current tool on the hovered tile |
| E | Buy the next grid expansion |
| `-` / `=` | Step the tier you're buying down / up (droppers and upgraders have tiers) |
| K / L | Save / load. Temporary, until the menu's Continue is the only way back in |
| Space | Pause / resume the simulation (freezes the world; you can still build and pan) |
| `` ` `` | Toggle the debug overlay (FPS, tool/facing, tick + entity/item counts, hover cell, camera) |
| Esc | Quit |
| `` ` `` | Toggle the debug overlay (FPS, tick, drawn/total entity + item counts, hover cell, camera) |
| Esc | Pause menu (resume / main menu). On the title screen, Esc quits |

The hover preview tells you what a click will do before you make it: green places,
hard red means the cell is taken or still locked, and faint red means the cell is
fine but you cannot afford the piece.

Money is always on screen; everything else in that list lives behind the debug
overlay.
The money, the shop, the controls bar and the inspect panel are always on screen.
The frame timings and the counts live behind the debug overlay.

## Hacking on it

Expand Down Expand Up @@ -140,6 +155,10 @@ src/
game.h/.c # game state, config, input, the sim, and the four loop entry points
render_game.h/.c # draws the world (iso tiles, shaded blocks) and the HUD
world/ # entities and the grid simulation
effects.h/.c # transient real-time visuals (an ore banking, an ore tipping off)
sprites.h/.c # the baked-in art registry
menu.h/.c # title screen and pause overlay
save.h/.c # save/load, one versioned binary slot

mach.c # unity root: MACH_IMPLEMENTATION + game sources + main()
game_lib.c / host.c # the hot-reload pair (dev builds only)
Expand All @@ -157,12 +176,14 @@ the mach.h repo, not here.
Entities are **fat structs**, not generic bags of components:
```c
typedef struct {
i32 grid_x, grid_y;
Direction dir; // upgraders move items too
i32 upgrader_id; // the bit it sets in an item's "distinct upgraders" mask
i32 upgrader_id; // the bit it sets in an item's "distinct upgraders" mask
i32 tier; // 1..MAX_TIER; scales how hard it lifts an ore's ceiling
} Entity_Upgrader;
```

The cell, the facing and the type live in the common `Entity`; each type's struct
carries only what is its own.

The `World` keeps entities and items in flat arrays, with grid indices for "what's
on this cell":
```c
Expand All @@ -173,11 +194,20 @@ typedef struct {

Item items[MAX_ITEMS];
i32 item_grid[256][256]; // item id at each cell, or 0 for empty
i64 money;

u64 upgrader_ids_used[UPGRADER_WORDS]; // which upgrader ids are taken
i64 money; // spendable: furnaces add, buying spends
i32 playable_side; // the unlocked square, a centered power of two

World_Event events[MAX_WORLD_EVENTS]; // what the sim did this tick that should be
i32 event_count; // seen; the renderer drains it every frame
i32 tick;
} World;
```

The event queue is the seam that keeps visual timing out of the sim: the sim records
*that* an ore banked, never how long the payout should take to animate.

Game code just loops over the arrays and updates things. No indirection to chase,
no query system to fight, and the performance is whatever you can read off the
page. The whole `World` is a single allocation out of an arena.
Expand Down
52 changes: 31 additions & 21 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ the engine as a committed header at `src/mach.h`.
Verified: the game builds and runs on the linux box with those installed.

- RE: gdd, think city skylines mixed with miners haven and a pinch of factorio.
The design doc (docs/gdd.typ, v0.2.0) is authoritative and now describes the
The design doc (docs/gdd.typ, v0.5.0) is authoritative and now describes the
value-loop builder in full; the Gameplay section below tracks catching the code
up to it.

## Gameplay (value-loop belt builder)
Design target: docs/gdd.typ (v0.2.0). Items below the "value model rework" are
Design target: docs/gdd.typ (v0.5.0). Items below the "value model rework" are
mechanics the GDD calls for that the code hasn't caught up to yet, in priority order.

- [x] Entity system (fat structs + direct arrays)
Expand Down Expand Up @@ -60,7 +60,8 @@ mechanics the GDD calls for that the code hasn't caught up to yet, in priority o
Entity_Furnace, world_spawn_furnace, and the UI strings). GDD naming decision.
The furnace also became a shallow walled bin visually, not a tall block.
- [ ] Machine tiers plumbing: a `tier` field scaling one stat. Dropper/upgrader tiers
are a value bump today (drop_cooldown, UPGRADER_MULT). Belt-speed tiers need belt
are a value bump today (ITEM_BASE_VALUE * tier, UPGRADER_CEILING_MULT +
UPGRADER_TIER_STEP). Belt-speed tiers need belt
speed moved off the global sim clock onto a per-entity ticks-per-cell cadence, with
item interpolation spanning that multi-tick window instead of one-move-per-tick.
- [x] **Splitter (GDD Milestone 3.5, the missing half of the loop).** A belt surface with two
Expand All @@ -82,8 +83,9 @@ mechanics the GDD calls for that the code hasn't caught up to yet, in priority o
that the round-robin splitter proves the two-output plumbing.
- [x] Save/load a layout (full world state: objects + tiers, ore in flight, money,
unlocked grid size, camera). One slot, versioned binary blob in save.c, written
field by field for cross-platform stability; grids rebuilt on load. K saves, L
loads (until the menu's Continue wires game_load). Bad/missing files fail cleanly.
field by field for cross-platform stability; grids rebuilt on load. The menu's
Continue loads; K / L are the in-game shortcuts. Bad/missing files fail cleanly.
Format is at v3 (the widened upgrader bitmaps); v1 and v2 files still load.
- [ ] Open (see GDD Open Questions): one furnace only (single sink, strong spatial
constraint) vs many placeable; whether furnaces become a 4th upgrade axis

Expand All @@ -92,9 +94,12 @@ mechanics the GDD calls for that the code hasn't caught up to yet, in priority o
side faces), depth-sorted painter's order by gx+gy, edge outlines
- [x] Placement validation: red/green hover preview
- [x] Facing arrows that read clearly in the iso projection
- [ ] Viewport-cull entities in render (only ground is culled today; game_render_draw
draws every entity regardless of the visible grid bbox we already compute).
The scaling win once belt counts get large, well before the animation math costs.
- [x] Viewport-cull entities and items in render. Culled in SCREEN space, not on the
ground bbox: a block's height and an ore's value label hang outside the cell by an
amount measured in pixels, not cells, so a cell-counted margin is right at one zoom
and pops machines at every other. The backtick overlay reads drawn/total so a bad
margin is visible. A headless probe asserts the property that matters: never cull
something visible.
- [x] Sprite pipeline: nob bakes assets/sprites/*.png into a generated header, the game
decodes them at startup (mach.h v0.1.4's mach_r2d_texture_from_memory) and uploads
with nearest filtering. Nothing is read from disk at runtime, so the single static
Expand All @@ -114,7 +119,7 @@ mechanics the GDD calls for that the code hasn't caught up to yet, in priority o
- [x] Pause menu: Escape (escape_quits now off) freezes the game and pops a scrim +
panel over it, Resume / Main Menu. Escape again resumes. In menu.c.
- [x] HUD spread to the screen edges via Clay floating panels: status top-left,
inspect top-center, F3 debug bottom-left, controls bottom-center.
inspect top-center, debug bottom-left (backtick), controls bottom-center.
- [x] Hover inspect panel (WTHIT-style): names the machine under the cursor, facing,
dropper cooldown, furnace banked total, and the ore's value / ceiling.
- [x] Pause / resume the simulation (Space): freezes sim ticks and animation; build and
Expand All @@ -123,9 +128,9 @@ mechanics the GDD calls for that the code hasn't caught up to yet, in priority o
next placed piece will use.
- [x] Debug/info overlay on the backtick key (fps, tick/entity/item counts, hover cell,
camera, controls), toggled and small so it stays out of the way.
- [ ] Interactive UI: build clickable UI on top of it: a tool palette, and the
economy's shop / grid-expansion buttons. Needs "UI consumed this click"
priority over world placement (Clay_PointerOver before place_at_hover).
- [x] Interactive UI: the right-hand shop panel (tools, tiers, grid expansion), clicks
consumed by the UI before world placement (pointer_over_ui, set from Clay_PointerOver
over every HUD panel, checked before place_at_hover).

## Feel & polish (backlog)
Things to make it play and look right, batched for later sessions. Not urgent.
Expand All @@ -135,12 +140,17 @@ Things to make it play and look right, batched for later sessions. Not urgent.
direction (real-time, so it runs even when the belt is empty)
- [ ] Sprites for pieces and items, replacing the flat shaded blocks/diamonds
- [x] Item despawn handling. Deleting an entity despawns the ore on its cell; ore that
hits a dead end (off-grid edge, bare ground, or a dropper's back) tips off and drops
out over FALL_TICKS with a sink-and-fade effect. See world_despawn, item_begin_fall,
world_update_falls.
hits a dead end (off-grid edge, bare ground, or a dropper's back) tips off and is
killed that tick. The sim emits a World_Event (FELL / BANKED) and the renderer turns
it into a real-time Effect with the sink-and-fade: visual timing lives in effects.c,
never in the sim. See world_despawn / item_kill (world.c), game_sync_effects (game.c).
- [x] Tune speed and feel: chunky tier-1 baseline, belts 3 cells/s (SIM_TICKS_PER_SEC),
an item every 2 cells (DROP_PERIOD), chevron scroll matched to belt speed.
Value curve (ITEM_BASE_VALUE, UPGRADER_MULT) left as-is; revisit with the economy.
Value curve (ITEM_BASE_VALUE, UPGRADER_CEILING_MULT, UPGRADER_TIER_STEP,
UPGRADER_CLIMB_DIVISOR) left as-is; revisit with the economy. NOTE: the drop cadence
was off by one until the audit batch (droppers fired every 3 ticks, not 2), so every
number in the economy was tuned against 2/3 of the real income. It all wants a
playtest pass.

## Content
- [x] Asset embedding: build-time bake of assets/sprites/*.png into the executable.
Expand All @@ -149,11 +159,11 @@ Things to make it play and look right, batched for later sessions. Not urgent.
- [x] Data serialization (save.c: versioned binary reader/writer; feeds save/load)

## Docs
- [ ] Go over the GDD (docs/gdd.typ) and bump its version (docs/VERSION). It's
drifted from the build: the Scope/Rendering sections still describe the
stack as SDL3 / `SDL_Renderer`, which the RGFW + own GL batch renderer
replaced, and the mach engine now lives in its own repo. Reconcile the doc
with the shipped engine and value model, then bump docs/VERSION.
- [x] Reconcile the GDD, README and TODO with the code (docs/VERSION -> 0.5.0). The GDD
had drifted: it cited deleted symbols (FALL_TICKS, item_begin_fall), claimed sprites
shipped when assets/sprites/ is empty, described a 2x2 starting region that is really
4x4, sold belt tiers as a third live upgrade axis, and listed four object types after
the splitter made five. The stack description (SDL3) had already been fixed earlier.

## Dev tooling
- [x] Hot reload: game logic compiles to a shared lib the host (src/host.c)
Expand Down
2 changes: 1 addition & 1 deletion docs/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.4.0
0.5.0
Binary file modified docs/gdd.pdf
Binary file not shown.
Loading
Loading