Solo project · design, code and art
Hollowmere
A grimdark top-down RPG where the only light that shows you the dead runs out while you hold it.

The core loop
Two lights, and only one of them tells the truth
Every design decision in the game hangs off one rule: the safe light is useless against the thing that kills you.



The wager is the whole game
Walk lit and burn charge, or walk blind and save it. Because the lamp both reveals and banishes, every encounter spends the same resource twice.
The HUD reads in peripheral vision
Health and lamp are liquid orbs, not bars — a moving surface catches the eye where a rectangle doesn't. You watch them while walking into the dark, not by looking at them.
Feedback is always diegetic
A refused action flashes the slot red. The carried light pulses its own belt slot. Exhaustion makes the stamina bar breathe. No text tells you any of it.
Combat
Committed swings, and a bar that punishes panic
Attacks run wind → strike → recover and cannot be cancelled. Facing follows the cursor, so aiming and moving are separate decisions.



Stamina is ten squares, and no numbers
A swing costs a third of a square. Sprinting costs one per second. Empty it and you move and swing 30% slower until a whole square returns — the bar pulses the entire time, which is the only signal the penalty is on.
Never take the sword away
Running out of stamina makes you slow, not helpless. Attacks are still allowed at zero. The penalty stays legible without ever removing the player's only verb.
Atmosphere
Weather, grain and a vignette, drawn every frame
320 raindrops, each with its own landing height so the rain strikes the ground all over the screen rather than at the bottom edge. A film grain and a vignette sit over everything.

Lighting order is a design decision
Anything drawn before the lighting pass obeys the darkness; anything after is self-luminous. Every element is deliberately assigned to one side — which is why the HUD stays readable in a pitch-black cellar.
Audio is synthesised, not sampled
There are no sound files. Rain, wind, crickets, the sewer hum and every hit are generated with Web Audio at runtime, which is what keeps the whole game inside one file.
Systems
One surface language, four panels
Inventory, journal, skills and shop are the same leather-and-brass frame. Nothing in the UI uses a second visual vocabulary, so a new panel is never a new thing to learn.




Text below 8px is hand-plotted
Anything small is drawn from a 3×5 bitmap font rather than fillText, which turns to mush under the pixelated upscale. Counters and quantities are all bitmap glyphs.
Quest markers cannot lie
The ? above an NPC is computed from the same branch chain as their dialogue, in the same order. If he has something to say, he wears a mark — the two can never disagree.
World and story
Six areas, told through what people left behind
Most of the story is optional and physical: letters on bodies, notes in the mud, a key that belonged to somebody's brother.




Migration
Porting to Godot, one system at a time
The plan is deliberately unglamorous: finish the design in the fast build, then port it once the design has stopped moving.




The art is the code.
There are no sprite files to import. A character is a function that draws a body from parameters, so porting the art means re-deriving the renderer, not copying assets. One shared function draws the player, four villagers, skeletons, the zombie, the boss and the guards, separated only by palette.
Two different lighting models.
The original composites a lighting canvas over the world, so what obeys the darkness is decided by draw order. Godot uses CanvasModulate with 2D lights, where it is decided per-node. Every element had to be re-classified as darkened or self-luminous, deliberately.
Determinism had to be preserved.
Prop placement is generated from a seeded RNG. To get the same village rather than a similar one, the generator was ported bit-for-bit, matching JavaScript's 32-bit integer semantics exactly.
Nothing is trusted without a comparison.
Both builds gained a screenshot harness that poses a scene and renders it headlessly. Ported output is checked against the original frame by frame — the shared character renderer came back identical on 2,488 of 2,496 sampled pixels, and the eight that differed were the pulsing eyes, which are time-based.
Where the port actually stands
Ported: world geometry, collision, movement, the character renderer, the lighting model, weather, and the HUD. Not ported: combat, enemy AI, inventory, dialogue, quests and audio.
That split is the honest one. The Godot build is a proven feasibility spike, not a second game — it answered the expensive question (can this be reproduced faithfully?) and the answer was yes. Continuing it now would mean designing and porting at the same time, so the design gets finished in the build where iteration is fastest, and the port resumes when saves, controllers and a desktop release actually matter.
Status