Add height zones, jetpack boost particles, and TigerStyle guidelines

Level generator: add vertical variety with tall levels (46 tiles).
Segment generators accept ground_row parameter, SEG_CLIMB connects
height zones, transitions inherit predecessor ground row to prevent
walkability gaps. Climb segments respect traversal direction.

Jetpack boost: add blue flame particles during dash (burst + trail)
and continuous idle glow from player back while boost timer is active.

Camera: add 30px vertical look-ahead when player velocity exceeds
50 px/s.

Fix flame vent pedestal in gen_pit to use ground-relative position
instead of map bottom (broken in tall HIGH-zone levels).

Add TigerStyle coding guidelines to AGENTS.md adapted for C11.
Add tall_test.lvl (40x46) for height zone validation.
This commit is contained in:
Thomas
2026-03-01 14:57:53 +00:00
parent 9d828c47b1
commit ad2d68a8b4
8 changed files with 813 additions and 192 deletions

View File

@@ -129,6 +129,100 @@ Paths are forward-slash, relative to `src/` or `include/`: `"engine/core.h"`, `"
- `snprintf` / `strncpy` with explicit size limits for strings
- `ASSET_PATH_MAX` (256) for path buffers
## TigerStyle Guidelines
Follow the principles from [TigerStyle](https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md),
adapted for this C11 codebase. The design goals are **safety, performance, and developer
experience**, in that order.
### Safety
- **Simple, explicit control flow.** No recursion. Minimal abstractions — only where they
genuinely model the domain. Every abstraction has a cost; prefer straightforward code.
- **Put a limit on everything.** All loops and queues must have a fixed upper bound. Use
`MAX_ENTITIES`, `MAX_ENTITY_SPAWNS`, `MAX_EXIT_ZONES`, etc. Where a loop cannot terminate
(e.g. the game loop), document why.
- **Assert preconditions, postconditions, and invariants.** Validate function arguments and
return values. A function must not operate blindly on unchecked data. In C, use `assert()`
or early-return checks with `fprintf(stderr, ...)` for runtime-recoverable cases.
Split compound conditions: prefer `assert(a); assert(b);` over `assert(a && b);`.
- **Assert the positive and negative space.** Check what you expect AND what you do not expect.
Bugs live at the boundary between valid and invalid data.
- **Static allocation after initialization.** Fixed-size arrays for collections (`MAX_ENTITIES`
pool, tile defs). Dynamic allocation (`calloc`) only during level loading for tile layers.
No allocation or reallocation during gameplay.
- **Smallest possible scope for variables.** Declare variables close to where they are used.
Minimize the number of live variables at any point.
- **Hard limit of ~70 lines per function.** When splitting, centralize control flow (switch/if)
in the parent function and push pure computation into helpers. Keep leaf functions pure.
"Push `if`s up and `for`s down."
- **Zero compiler warnings.** All builds must pass `-Wall -Wextra` with zero warnings.
- **Handle all errors.** Every `fopen`, `calloc`, `snprintf` return must be checked. Early
return on failure.
### Performance
- **Think about performance in the design phase.** The biggest wins (1000x) come from
structural decisions, not micro-optimization after the fact.
- **Back-of-the-envelope sketches** for the four resources: network, disk, memory, CPU.
For a game: frame budget is ~16ms at 60 Hz. Know where time goes.
- **Optimize for the slowest resource first** (disk > memory > CPU), weighted by frequency.
A cache miss that happens every frame matters more than a disk read that happens once at
level load.
- **Batch and amortize.** Sprite batching via `renderer_submit()`. Particle pools. Tile
culling to the viewport. Don't iterate what you don't need to.
- **Be explicit with the compiler.** Don't rely on the optimizer to fix structural problems.
Extract hot inner loops into standalone helpers with primitive arguments when it helps
clarity and performance.
### Developer Experience
- **Get the nouns and verbs right.** Names should capture what a thing is or does. Take time
to find the right name. Module-prefixed functions (`player_update`, `tilemap_load`) already
enforce this.
- **Add units or qualifiers last, sorted by descending significance.** For example,
`latency_ms_max` not `max_latency_ms`. Related variables then align: `latency_ms_min`,
`latency_ms_avg`.
- **Always say why.** Comments explain rationale, not just what the code does. If a design
decision isn't obvious, explain the tradeoff. Code alone is not documentation.
- **Comments are sentences.** Capital letter, full stop, space after `/*`. End-of-line
comments can be phrases without punctuation.
- **Order matters for readability.** Put important things near the top of a file. Public API
first, then helpers. In structs, fields before methods.
- **Don't duplicate variables or alias them.** One source of truth per value. Reduces the
chance of state getting out of sync.
- **Calculate or check variables close to where they are used.** Don't introduce variables
before they are needed. Minimize the gap between place-of-check and place-of-use.
- **Descriptive commit messages.** Imperative mood. Inform the reader about the why, not
just the what. The commit message is the permanent record — PR descriptions are not
stored in git.
### Zero Technical Debt
Do it right the first time. Don't allow known issues to slip through — exponential-complexity
algorithms, unbounded loops, potential buffer overflows. What we ship is solid. We may lack
features, but what we have meets our design goals. This is the only way to make steady
incremental progress.
## Architecture Patterns
### Entity System