Pixel Knights

A small pixel island in your sidebar, with a castle, a village, and the units
that hold them. It reads out your code. Errors land raiders on the shore, and
fixing them lets the garrison cut them down. Warnings, uncommitted work, and your
build and test tasks show up on the island too. The whole thing is one canvas in
a webview. No accounts, no tracking, and nothing leaves
your machine.

Features
- An island of stacked elevations, drawn from the Tiny Swords tileset. From the top: a monastery with its Monk, a row of hillside houses, an archery range, the castle, the barracks, and a village on the ground. Every elevation of the fort has a tower on its corner
- A garrison that lives where it is posted. Archers on the walls, Lancers guarding the monastery, Warriors at the barracks. No unit in armour starts on the shore, because the ground belongs to the Pawn
- Grass stairs cut into each cliff, on opposite sides, so the elevations zigzag. The units walk them. Soldiers come down from the upper elevations, garrison figures leave their posts to walk the village, and the Pawn carries wood up the stairs to repair the buildings
- Raids driven by your errors. One red raider per error, up to three. Every Archer on the island opens fire at once, and a Warrior and a Lancer come down the stairs to meet them
- More of your code on the island. Warnings are Red Pawn thieves who carry gold out of the village houses and pile it on the shore. Fix one, and the village Pawn throws a thief into the sea and takes the gold home. Uncommitted files bring out Pawns to haul work up to the castle, and a commit delivers it. Commits you have not pushed wait at the castle gate, and merge conflicts set the castle on fire. A running build task sets the Pawns hammering, a failing test task sets a roof on fire, and a passing one has the Monk heal the knight
- Your debugger too. A debug session brings the rubber duck into the shallows, and a breakpoint stops the knight in its guard
- The island remembers. It counts raiders slain, commits delivered and days on the island. The count stays on your machine, in VS Code's own storage, and shows in the status bar tooltip
- Day and night. The island grows darker in the evening by your local clock, and lighter again in the morning
- The island scrolls. It is about 1,100px tall, taller than most sidebars, so the pane shows part of it and scrolls. It opens on the shore, and if raiders land while you are scrolled up to the castle, it scrolls back down to them
- The layout is worked out from the width of the pane, so the scene rebuilds when you make the sidebar wider or narrower instead of getting cut off
- Four colours, blue, black, purple and yellow, changed live from settings. The units and every building change together
- A status bar entry opens the view, and
Pixel Knights: Focus Companion View does the same from the command palette
Pixel Knights: Stage a Practice Raid lands three raiders that fall one by one, so you can watch a raid without breaking your code. Pixel Knights: Copy Island Record copies the island's count to the clipboard
Install
Search Pixel Knights in the Extensions view, or:
code --install-extension nauqh.pixel-knights
How the island reacts
The extension sends the state of your code to the island. That is every
error, with its file and line. It is also the
number of warnings, uncommitted files, files in conflict and
commits not pushed, whether a build task is running, whether the last
test task failed, and whether a debug session is running or stopped.
Everything below is the island's reading of that. It responds to whatever
produces your diagnostics, such as a language server, a linter or a compile
task, and not to any particular editor event.
| Error count |
What happens |
| Rises above zero |
One red raider per error wades in from the right shore, up to three, and they line up on the beach. Every Archer on the island opens fire from where it already stands, so the island answers before anything has moved |
| Stays above zero |
The garrison turns out. The knight, which is one of the barracks Warriors, comes down and repeats two attacks and a guard. One of the two Lancers leaves the monastery and runs down every stair on the island to attack from the second row. Both start where they are posted, so the knight arrives in about 5 seconds and the Lancer, starting from the top, in about 16 to 26, longer the wider the pane. Everything else holds its post, and the Pawn and the Sheep get out of the way |
| An error is fixed |
A raider dies in a puff of dust, always the one nearest the fight. Errors are followed one by one rather than counted, so fixing one error while another appears kills a raider and lands a new one |
| Reaches zero |
The raid ends and both units walk back up the stairs to their own elevation |

The rest of your code shows up like this:
| Your code |
What happens |
| Warnings |
One Red Pawn thief comes ashore, however many warnings there are. It goes into a house in the village and carries a load of gold to the bottom shore, and keeps making trips until there is one load per warning, up to six. The gold is one pile that grows with every load, using the pack's six gold stones from smallest to largest. Nobody fights it while the warnings stand, because warnings are known about, not urgent. When a warning is fixed and others remain, the village Pawn comes down and carries one load of gold back to the house, one trip per fixed warning. When the last one is fixed, the Pawn first hits the thief with its hammer, which throws the thief spinning through the air and into the sea with a splash, then carries all the gold home. A thief still wading in, or caught with gold in its arms, just runs off. While the thief is ashore, the button at the top says Thief ashore instead of Island at peace |
| Uncommitted files |
A Pawn comes out to haul work up to the castle, and a second one at 10 files or more. A pile of work grows at the castle gate, from one load up to three at 15 files. The village Pawn works more often too |
| A commit |
The chronicle calls the quest complete and counts the files in the commit. The haulers go home, and the pile goes with the count. A checkout or a rebase moves HEAD as well, but is not counted as a commit |
| A build task running |
Every Pawn stops where it is and hammers until the build ends, then carries on with what it was doing. Watch tasks, which never end, are left out |
| A test task failing |
A village roof catches fire and keeps burning until a test task passes |
| A test task passing |
The Monk heals the knight, as soon as the knight is out of doors |
| Commits not pushed |
One load of stores per commit waits on the right of the castle gate, up to three. The uncommitted pile is on the left, so a commit moves the work across the gate. A push ships the stores. A branch with no upstream shows none, and a reset that drops the commits reads as a push |
| Merge conflicts |
The castle catches fire on both decks and at the gate, and burns until the conflicts are resolved. On a narrow pane the tower that stands in for the castle burns instead |
| A debug session |
The rubber duck leaves its corner of the sea and paddles into the shallows under the shore until the session ends |
| Stopped at a breakpoint or a step |
The knight stops where it is and holds its guard until the program runs on. A raid still comes first |
Tests here means a task in the Test group, such as npm test. Results shown in
the Test Explorer are not visible to other extensions, so they do not reach the
island. The git readouts need VS Code's built in git extension. Without it the
village is simply never busy.
The record counts an error as a raider slain when it goes away, even when it went
because its file was closed. It is kept in VS Code's global storage, so it is the
same in every workspace, and it never leaves your machine. The practice raid is
not counted.
Diagnostics are debounced by 300ms, and a state identical to the last one is
dropped rather than posted, so a busy language server does not wake the render
loop.
The garrison
Every soldier is posted at a building. It stands either on the deck on top of
that building or on the ground in front of it. Archers go on top, because an
Archer is only worth having where it can see. The rest stand at the gate.
| Building |
Where |
Who |
| Castle |
Deck |
1 Archer |
| Tower |
Deck |
1 Archer. There is one tower on each elevation of the fort: the archery range, the castle and the barracks |
| Barracks |
Ground |
2 Warriors. One of them is the knight |
| Monastery |
Ground |
2 Lancers, who walk the open grass on the right of the monastery rather than standing on a spot, each in its own half. The one in the half nearer the stair is the one that goes out to fight. The Monk stands in front of the door, now and then walks down the island and back, and stays out of raids |
The guard is deliberately small. A castle elevation is about 190px of ledge at a
normal sidebar and the castle itself covers 156 of it, so a bigger guard fills
the whole ledge and reads as a queue rather than as a garrison.
Each elevation has its stair at one end, and the two tiles of ground beside that
stair reach one row further down than the rest. The tower stands on the front
edge of that step, level with the top of the stair beside it. The two elevations
above the fort are not walled, so a tree stands there instead. The stairs alternate sides, counted up from the barracks, whose
stair is always on the right. That is the beach side, which keeps the knight's
run down to a raid short. It also means the tower never competes with the castle
for space, so every tower appears at every pane size.
The castle and the barracks do need room, so what else you get depends on the
pane.
| Sidebar |
What the island posts |
| About 300px wide or more |
Everything. 4 Archers, 2 Lancers, 2 Warriors and the Monk |
| About 250 to 300px wide |
Every elevation, but too narrow for the castle, so a tower stands in its place. The Lancers guard the monastery, so they are still there |
| Under about 250px wide |
Too narrow for stairs on both sides, so only the castle elevation, with a tower in place of the castle. You get 2 Archers and the knight |
Greenery on the upper levels
Bushes, rocks and the two short trees are scattered on the elevations as well as
on the ground, following what Pixel Frog does in the pack's own promo art, where
scrub grows along the lip of every cliff.
Two rules decide where it goes, and both come from the size of the ledge:
- It sits on the lip, not spread over the whole elevation. An elevation is
three tiles of grass and its building stands taller than all three, so
anything placed behind a building is not partly hidden, it is completely
hidden.
- If there is no room beside the building, it grows at the foot of the wall
instead, at the building's outer corners, drawn over the base stonework.
Never across the gate, and never where a guard is standing.
That second rule is what makes it work at sidebar width. The castle elevation is
160px of ledge at a 300px pane and the castle covers 156 of it, so there is
nothing to plant beside. The gap does not reach the 56px a bush needs until the
sidebar is about 420px wide.
The monastery and the hillside houses are the exception. Their buildings are
short enough to leave the back of the elevation showing, so trees also grow
along the back of those two, clear of the buildings, the way the pack's own
banner crowds its hills.
Only Tree3 and Tree4 are used up here. Tree1 and Tree2 stand 120px, taller than
the elevation they would be standing on. The trees up here are scenery and are
never cut, so the woodland the Pawn works stays on the ground, a walk from his
yard rather than a climb.
When nothing is wrong
Which is most of the time, so the island is built to be worth leaving open. Every
elevation can be walked on, and the stairs are the only way between them.
| Who |
What they get up to |
| The knight |
Walks up and down the barracks elevation it is posted on. Every few minutes it takes a longer walk, down into the village or up to the castle, then comes back |
| The Pawn |
Wood is the island's only resource. The Pawn takes an axe to a tree, fells it, sets the axe down by the stump, carries the log home and stacks it on the woodpile beside his house, then goes back for the axe. From the woodpile he splits a log at the chopping stump in his yard and carries the firewood indoors, or takes a log to a building anywhere on the island and hammers at it to repair it. The woodpile holds three logs. He fells while it has room, and splits or repairs while it has logs |
| Trees and stumps |
A felled tree leaves its own stump, a pine's for a pine and a birch's for a birch, for a minute or two, and then grows back. The last standing tree is never cut |
| The Lancers |
Wander the grass on the right of the monastery, walking to a spot, standing a while, then picking another. When raiders land they stop where they are, apart from the one who goes down to fight |
| The garrison |
A Lancer or a Warrior leaves its post now and then and walks the island. Only one at a time, so an elevation is never left empty. The Archers never leave at all, because they are the wall |
| Going indoors |
Buildings are solid, so a unit on a deck has to use the door. It steps inside at the foot of the building, comes out on top, and does the reverse on the way back. A unit posted on the ground just walks |
| Everyone |
Steps inside a building for a while and comes back out |
| The Monk |
Stands at the monastery. Every few minutes he walks down to another elevation and climbs back |
| The Sheep |
Grass |
None of it runs to a fixed script. Each unit picks its own jobs, and the long
ones have a wait between them, so the island is quiet more often than it is busy.
The moment a raid lands it all stops. Anything above the ground walks back down
the stairs, anything indoors comes out, and the line forms.
Nothing is remembered between sessions yet. See plan.md.
The chronicle
The button at the top right of the view shows the pack's shield icon while the
island is at peace and its sword once there are errors, with the pack's arrow
pointing down, or up while the panel is open. It opens a panel with two parts. Live
activity says what each unit is doing right now. The chronicle is a log of
what has happened, written like the chat log of an old strategy game: a time,
then who did what, with names and items in brackets.
| Line |
When it is written |
The garrison stands watch. |
The view opens |
Raiders sighted off the eastern shore! |
The first error appears |
A [Red Raider] wades ashore. (app.ts:12, 2 errors) |
Each raider lands, up to three, with the file and line of its error |
[Knight] rallies the garrison! |
The raid starts |
More raiders gather offshore. (util.ts:40, 5 errors) or A raider offshore turns back. |
An error past the third appears or is fixed, where the beach cannot show it |
[Knight] slays a [Red Raider]! (app.ts:12, 1 error left) |
A raider dies because an error was fixed. If the Knight has not reached the fight yet, the Lancer or the Archers get the credit |
Victory! The shore is clear. |
The last error is fixed |
[Pawn] fells a tree. |
A tree comes down |
[Pawn] receives loot: [Wood]. |
A load is picked up. Items show the pack's own icon |
[Pawn] stacks [Wood] on the woodpile. |
A log reaches the woodpile |
[Pawn] splits a log into firewood. |
A log is split at the chopping stump |
[Pawn] repairs the Barracks with [Wood]. |
A log is used to repair a building |
[Monk] heads down to the Archery Range. |
A unit sets out for another elevation |
A [Red Pawn] sneaks ashore. (2 warnings) |
A thief comes ashore for a warning |
A [Red Pawn] steals [Gold] from the House. (2 gold on the shore) |
The thief puts a load of gold on the shore |
[Pawn] kicks the [Red Pawn] into the sea! (0 warnings) |
The last warning is fixed and the village Pawn throws the thief out. A thief still on its way slinks back out to sea instead |
[Pawn] brings the [Gold] back to the House. |
A warning is fixed and the village Pawn carries a load home. After the thief is kicked out it can be brings 3 loads of [Gold] |
[Pawn] brings the [Gold] back to the House. |
The stolen gold is home |
A [Pawn] comes out to haul. (7 uncommitted files) |
A hauler comes out |
Work piles up at the Castle. (7 uncommitted files) |
The pile of uncommitted work grows |
Quest complete: 7 files delivered. |
You commit |
The builders down tools. The build is done. |
A build task ends, or The build failed. |
Fire in the village! The tests failed. |
A test task fails |
[Monk] heals the [Knight]. The tests pass. |
A test task passes, starting with The fire is out. if the last one failed |
Each line is written at the moment the thing happens, so a Pawn carrying wood
for ten seconds is one line, not ten. The panel is framed in the pack's own
SpecialPaper art and keeps the last 40 lines. If you scroll up to read older
lines, new ones do not pull you back down.
A file and line in brackets is a link. Click it, or press Enter on it, to open
the file at that line. It only opens files the extension has reported an error
in.
The island
Tiny Swords is drawn at 1x on a 64px world grid, which is far too big for a
sidebar, so the scene is not simply scaled down with CSS. Two rules keep the
pixels straight:
- Resize the art once, and only once. Each sheet is drawn one time into an
offscreen canvas at half size, with smoothing off. Every draw after that
reads from that cache at 1:1. When the pane is wide enough to be worth it,
the whole viewport is scaled up by an integer factor, never a fraction.
- Integer positions. No sprite ever lands on half a pixel, so nothing
shimmers as it moves.
Units are not resized to a common body height. Each one stands on the ground
contact point drawn into its own shadow, which is why the Lancer, whose frame is
mostly spear, comes out the right size next to the Warrior. Draw order sorts on
that same contact point, so whoever is further down the screen is drawn last.
The terrain is built from the pack's tilemap rather than from pre-cut images. It
uses a nine slice for the water edge, another for each elevation, and a stone
wall row that stands on grass. The stairs are the pack's diagonal grass ramp, two
tiles tall, and each elevation's edge drops a row where the ramp meets it.
Without that drop the ramp looks like a mound instead of a way down.
Configuration
| Setting |
Default |
Does |
pixelKnight.colour |
colour1 |
Unit and building colour. colour1 is blue, colour2 is black. It applies to every unit and building at once. Raiders stay red either way |
Colour changes are sent to the open view straight away, with no reload.
Development
npm install
npm run build
Then press F5. That launches an Extension Development Host with Pixel Knights
loaded. Open it from the activity bar or from the status bar. npm run package
builds a .vsix you can install locally with code --install-extension. It
bumps the patch version in package.json first, so to rebuild the same version
run npx vsce package instead.
If you also have the Marketplace build installed, both use the same icon in the
activity bar. The launch config passes
--disable-extension=nauqh.pixel-knights so that only the workspace copy loads.
It does not pass --disable-extensions, which would switch off the language
servers too, and with no diagnostics there is nothing to raid you. The copy that
does load says which one it is. The view header reads Pixel Knights [DEV]
with the version next to it, and the status bar entry ends in [dev].
src/extension.ts startup, diagnostics, git and task watchers, webview host
src/world.ts what the host tells the island, shaped from plain data
src/sprites.ts the asset list: every sheet, and where it lives
media/companion.js the whole renderer: layout, tilemap, animation, sprites
media/tiny-swords/ the copy of the asset pack
docs/plan.md where this is going
npm run watch rebuilds src/ on save. The renderer is plain browser
JavaScript with no build step of its own, so editing media/companion.js only
needs a reload of the Extension Development Host.
One thing has to match across the two sides. The sprite keys in COLOUR_FILES,
SCENE_FILES and UI_FILES in src/sprites.ts build the list of URIs, and they must match
the keys the renderer looks up in the SPR and NATIVE tables in
media/companion.js. Adding a sprite means editing both files. One says where
the art is, the other says how big it is and where it stands on the ground.
If the sprite comes from a part of the pack that is currently excluded, you also
have to relax .vscodeignore, which keeps the unused teams out of the .vsix.
Credits
All artwork is from the
Tiny Swords pack by
Pixel Frog, used under its license
terms. The code is MIT, the artwork is not. See LICENSE and
THIRD_PARTY_NOTICES.md. Purchase
the pack from Pixel Frog.