Inspiration

We started from one constraint and refused to break it: if a rule of physics can act on the world, it must also act on the player.

Most games with a gravity gun or a time-slow quietly exempt the person holding it. That exemption is where the fantasy leaks — the moment you realise the power is a targeting reticle and not a law, the world stops feeling real. So we built AXIOM the other way round. Gravity inverts, and it inverts you. Fire spreads, and it spreads to you. Freeze stops a machine mid-swing, and it will stop you mid-jump if you are careless with it.

Everything else — the derelict ship, the missing crew, the machine with one directive left — grew out of asking who would build such a thing, and what they would do when it started working.

What it does

AXIOM: ECLIPSE-09 is a pixel-art puzzle-platformer in which the laws of physics are objects you carry off a dying starship and aim at everything aboard.

Four rules, each found in the room that explains it:

  • Gravity — re-aims and rescales the gravity vector for anything in range
  • Time — rescales simulation speed, including for the things chasing you
  • Fire — combustion as a property, not an attack: it ignites, damages, and propagates through stacked objects
  • Freeze — pauses a state rather than removing it, and remembers it

That last distinction is the design's spine. Freeze a burning crate and it resumes burning with exactly the time it had left. Freeze an armed canister and its fuse stops mid-count, then continues. Nothing is discarded; it is held.

Wrapped around that: nine rooms from the Crew Quarters to the Custodian Chamber, each with a gameplay reason to exist and none of them a filler corridor. Twelve optional logs, one to three sentences each, written to raise more than they answer. Corrupted security machines that turn out not to be attacking you because you are an intruder — they are trying to stop AXIOM from leaving. And a five-phase final boss whose shield breaks only to the one rule that phase names, so it is lost by misunderstanding rather than by attrition.

Play it free in the browser: https://axiom-eclipse-09.vercel.app

How we built it

Godot 4.6, GDScript only, GL Compatibility renderer, 320x180 native resolution at integer scale.

The architecture is the part we would defend hardest. RuleManager owns two deliberately separate layers — availability (discovered, acquired, activated) and influence (which fields currently reach which objects). Rules are RuleData resources; RuleBase drives them.

Everything in the game reacts through one component, RuleReceiver, and its two methods are the entire seam:

  • scaled_delta(delta) — where Time slows and Freeze stops. Any object that integrates its own motion through this is automatically correct under both rules.
  • get_gravity_pull() — signed vertical gravity, so bodies that move themselves respond to inversion instead of fighting it.

Burnable, Freezeable and Damageable layer on top without knowing about each other. Objects opt in by carrying a component, and dispatch is duck-typed on properties rather than class checks, so adding a new interactable never means editing a rule.

Supporting pieces: the nine rooms are generated by a Python build script so layout is data rather than hand-placed scene files; art is authored in Aseprite; the web build exports to Vercel.

Challenges we ran into

The font that ate its own spaces. Late in the build, every label rendered "SHIP ARCHIVE" as "SHIPARCHIVE". We diagnosed it by rendering the typeface at 8, 12, 16, 20 and 24 pixels: below 16px the space glyph's advance rounds to zero. The obvious fix — raise every label to 16px — is the one we shipped first, and it was wrong. 16px text on a 180-pixel-tall screen is enormous, and the UI became a wall of shouting. The correct fix was one resource: wrap the font in a FontVariation with spacing_space = 2, which restores word spacing at 8px. We had fixed the symptom and had to go back for the cause.

Sleeping bodies. "I can't manipulate anything" was the first real playtest report. Rigid bodies that have gone to sleep silently ignore both applied forces and changes to area gravity — so every rule looked broken while the code was provably correct. Bodies now wake on any rule change.

A death flag that was never cleared. Damageable set _dead and never unset it. Consequence: the boss ignored all damage after phase one, and a respawned player was permanently invulnerable. Two unrelated-looking bugs, one missing line.

Freeze cancelling itself. Freezing via FREEZE_MODE_STATIC caused physics areas to report body_exited, which dropped the rule field, which un-froze the object. Switching to FREEZE_MODE_KINEMATIC fixed it.

Tests could not see autoloads. Godot does not register autoloads under --script, so every suite that touches RuleManager or StoryState had to be restructured to run as a scene.

Accomplishments that we're proud of

  • Zero cross-rule knowledge in the codebase. Freeze pausing a burning object's remaining burn time, and a fire propagating through a Time-slowed room, required no code that mentions both rules. They compose because they share a seam.
  • A boss that tests understanding, not patience. The Custodian has five phases and roughly 90 seconds of mechanical floor under flawless play. Bring the wrong rule and it is not slow — it is unwinnable. We chose that deliberately over an HP sponge.
  • 15 test suites, 900+ assertions, all green — covering rule composition, fire propagation budgets, freeze state preservation, boss phase gating, room routing and the ending.
  • A 1.1 MB game. The web pack was 32 MB until we found that ten font-specimen JPEGs shipped with the typeface were being bundled into the build.

What we learned

Find the seam, then never violate it. Almost every hard bug in this project came from something bypassing scaled_delta() or reading a velocity that a modifier had already zeroed. Almost every feature that landed easily came from something routing through it. One well-chosen abstraction bought us more than any amount of per-rule special-casing would have.

Duck-type the contract, not the class. Checking whether a host has a gravity_scale property, rather than whether it is a RigidBody2D, is why new objects react to rules the day they are added.

"Obvious fix" and "correct fix" are different fixes. The font bug is the clean example, but not the only one — the instinct to make a symptom disappear cost us a full pass over 33 scene files that we then had to undo.

What's next for Axiom

  • Multiplayer. Deliberately unimplemented, but the architecture was built to accept it: rule state is already centralised and authoritative rather than scattered across objects. Two players carrying different rules in the same room is the obvious next design, and the Custodian's one-rule-at-a-time containment field was written with that in mind.
  • Audio. The honest gap — every sound slot is exported and currently null. The game is silent.
  • More of the ship. Nine rooms is the spine of a longer route, and the room builder makes adding decks cheap.
  • Accessibility. Remappable input, and a scale option for players who find 320x180 text small even at correct spacing.

Built With

  • godot
Share this project:

Updates

Submission history