Omahub
← All plugins
P

Omatower Defense

by perfektnacht

Tower defense on real Grand Prix circuits. Park Quattros, shoot down the takes about Omarchy with the screensaver's own effects.

Security review

No obvious issues detected

Deterministic scan — not a security guarantee

None
Risk level
None
Analyzed commit
ec41ac9
Scanned
1 month ago

No potentially dangerous behavior detected in the analyzed commit.

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

None
AI risk level
None
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
ec41ac9
Reviewed
1 month ago

This is a self-contained QML tower-defense game with no install-time scripts, no destructive commands, and no observable network, persistence, or credential-access behavior. It only reads the Omarchy theme palette and sanitizes file-derived text before display. The deterministic scan found no issues and the sampled code matches the benign gameplay description.

How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/perfektnacht/omatower-defense --enable
Other #bar #quickshell #games

Omatower Defense

Tower defense on real Grand Prix circuits, as a native Omarchy shell plugin.

You park Audi Quattros around a lap. The takes about Omarchy drive it — not a distro, bloatware, shell-script-slop, cachy-is-better, THE-NIXPILL — and if one completes the lap and crosses your start/finish line, you lose a life. The cars shoot back with the effects from Omarchy's own ttfx screensaver: binarypath, matrix, laseretch, fireworks, blackhole.

Everything is drawn in QML — no sprite sheets, no assets — and the whole game recolours itself from your current Omarchy theme, light or dark.

Omatower Defense at Spa-Francorchamps: Quattros parked around the lap, a laseretch beam through the pack

<sub>Spa-Francorchamps, wave 14, under the Ethereal theme. Every colour on screen came from colors.toml.</sub>

Install

omarchy plugin add https://github.com/perfektnacht/omatower-defense --enable

Or, to hack on it, symlink the checkout into your plugins directory and enable it:

ln -s "$PWD" ~/.config/omarchy/plugins/perfektnacht.omatower-defense
omarchy plugin enable perfektnacht.omatower-defense

Open it with the car icon in the bar, or bind a key to:

omarchy-shell shell toggle perfektnacht.omatower-defense

Hiding the overlay pauses the run rather than throwing it away, so you can duck out mid-wave and come back.

Switching workspace hides it too. A layer-shell overlay floats above every workspace, so without this the game would sit on top of whatever you switched to with nothing clickable underneath. Leaving its workspace closes the overlay and pauses the run where it stands; the bar icon brings it back untouched.

Without Omarchy

It also runs as a plain window on any Quickshell install:

qs -p .

Removal

omarchy plugin remove perfektnacht.omatower-defense

That disables it and deletes the plugin directory. If you installed by hand with the symlink above, the plugin directory is a link to your checkout, so remove the link and leave the checkout alone:

omarchy plugin disable perfektnacht.omatower-defense
rm ~/.config/omarchy/plugins/perfektnacht.omatower-defense

Nothing else to clean up: the game writes no config, no save files and no state outside its own plugin directory. It only ever reads your theme from ~/.local/state/omarchy/current/theme/.

Controls

Key Action
Enter Start the run from the circuit picker (←/→ choose a circuit)
1–7 Pick a car, then click a parking bay to park it
Left click Place, or select a parked car
Right click Cancel placement / deselect
Space Call the next wave early (pays a bonus), or pause mid-wave — and always resumes
P Pause. The board greys out, placement is locked, and resuming returns to 1×
U / S Develop / retire the selected car
T Cycle its targeting priority
Esc Cancel, then deselect, then close (closes straight away on the picker)
MENU End the current run and go back to the circuit picker (click twice to confirm)
R Restart after a loss

How it plays

Damage types. No single car clears everything.

Type Cars Notes
DATA Quattro 80, Quattro S1 Bread and butter, blunted by armour
BEAM Sport Quattro E2 Ignores armour entirely, but attenuates through a queue
BLAST Quattro RS Splash, but plenty of takes resist it
FIELD Quattro Concept Slows and chips; arch-btw is immune

The beam attenuates. Every take walks the same racing line in single file, so a beam that pierces five targets for full damage is effectively free multi-target: it is always lined up with the convoy. Each successive take it punches through takes 35% less — at tier IV that reads 495 / 322 / 209 / 136 / 88, so a full line is worth 2.53× one target rather than 5×. The E2 stays the long-range armour-ignoring sniper it is meant to be without being the only car worth parking.

Stealth is a hard gate. cachy-is-better is invisible to any car without packet inspection. The E2 has it natively, the Quattro 80 gains it at level 3, and a level-3 Pace Car shares it with everyone in range. Bring one or it walks straight past you.

Armour, shields and No Cash. Armour is flat mitigation that BEAM skips entirely. reply-guy carries a regenerating shield that wants burst damage, not chip. And syu-and-pray splits into two halves on death that pay nothing — as do the minions THE-ALGORITHM summons. Waves that pay no bounty are how a run quietly starves.

Economy and support. The Service Barge cannot shoot; it pays out at the end of every wave, and you may run at most two. The Pace Car cannot shoot either; it buffs fire rate and range for neighbours, and cleanses the tower stuns that ENGAGEMENT-FARM throws out.

Being stunned. When a stun lands, the HUD says how many cars it caught, the ticker says for how long, and every frozen car wears a pulsing ✖ ring with a draining timer and fades out — so a stun on the far side of the lap is something you see, not something you work out afterwards from the lives you lost. If a Pace Car absorbed the whole pulse, it says that too.

Targeting priority. Every damage car can be set to FIRST, LAST, STRONG or CLOSE. Left on FIRST, your guns chew on the tanky frontliner while the glass cannon behind it walks to your machine.

Bosses. THE-NIXPILL every 10 waves. Every 30 you get all three of the others at once: THE-ALGORITHM soaks the shots meant for what is behind it, ENGAGEMENT-FARM stuns your cars and hastes its friends, and DISTRO-HOPPER is fragile, fast, and costs ten lives if it gets through.

Where you can build. Cars go in fixed parking bays laid out beside the circuit, shown as soon as you pick a car from the shop. Bays are generated clear of the whole lap, so a car can never end up straddling the racing line no matter which way it rotates to aim. A tight circuit simply generates fewer of them — Spa has 39 bays, Monza 50 — and that is what makes a layout a level.

The first car starts the clock. Nothing is on a timer until you park something. Until then a ghost take drives a demonstration lap and arrows march along the asphalt, so you can read the route before committing to a bay.

Reading the board. Every parked car carries a four-bar rank plate off its tail: filled bars are tiers bought, empty ones are tiers left. It is bolted to the bodywork, so it turns with the car and stays off the circuit — you can see what every car is worth without clicking a single one.

Development tiers. Three tiers are available from the start. The fourth unlocks only after wave 30, and it is a big jump: the E2 goes from a three-target beam to a five-target one, the RS clears eighteen takes in a burst.

The debrief. Losing opens a summary of the run: how much damage every car type dealt, with a share bar and the car drawn beside it, and every take you shot down with its sprite, its name and how many of them you refuted. Damage is tallied by car definition, not by instance, so retiring a car on wave 12 does not erase what it did. From there you can run it back, change circuit, or exit.

Money. Bounties, wave bonuses and barge payouts all decay as the wave number climbs — a kill on wave 30 pays under half what the same kill paid on wave 5 — while enemy health grows superlinearly and then compounds every round past wave 30. Income is meant to lose that race. Service Barges are also capped at two per run, because the alternative is an economy that solves itself.

Circuits

Circuit Difficulty Character
Monza Rookie Huge straights, long sightlines, acres of paddock
Silverstone Rookie Fast and flowing with a slow Arena loop
Spa-Francorchamps Pro Enormous lap, so economy has time to pay for itself
Suzuka Pro The lap crosses itself; one car can cover both halves
Monaco Legend Barely any runoff. Every placement has to earn its space

Classic offers every car. Draft deals a random five-car hand each run — always including one detector and one economy car — so the same opening does not work every time.

Theming

The palette comes from ~/.local/state/omarchy/current/theme/colors.toml, live — run omarchy theme set and the game recolours without a reload. Both schemas are read (the named one and the older color0..color15), along with Aether's mode = "light" | "dark".

Switching repeatedly works. omarchy-theme-set does not edit colors.toml, it does rm -rf on the theme directory and mvs a new one into place, so every switch hands over a brand new inode. A file watch is attached to an inode, not a path: the first switch kills the watch along with the old file and every switch after it goes unnoticed. Worse, a read landing between the rm and the mv returns nothing, and a naive reader collapses to its built-in fallback and stays there. So the watch is re-armed when it fires, a one-second poll backs it up, and an empty read is never allowed to overwrite a good palette.

The whole palette also resolves in a single binding. It used to be a chain (raw → themeHues → concentration → collapsed → roles → red), and QML invalidates a chain by pushing change signals through it — a lazy link nobody has read yet never fires, so everything downstream keeps serving cached values. The symptom was a half-applied theme: backgrounds and text switched instantly while every car and every take kept the previous theme's colours.

A terminal palette does not owe a tower defense two things it needs, so the game derives them:

Seven separable accents. Aether's Monochromatic, Muted and Pastel modes legitimately collapse every ANSI hue onto one — a real shipped theme has red, orange and magenta as byte-identical blues. Damage types, affordability and seventeen distinct takes all stop reading. So the palette is measured: circular variance of its hues, weighted by the chroma each actually carries.

  • Distinct palette → the theme's own hues are used exactly as authored. If two specific roles are close enough to be confused, only the second of the pair moves, and it moves in lightness first so the hue survives.
  • Collapsed palette → rebuilt as an analogous fan across a 120° arc around the theme's own hue, with alternating lightness. It still reads as one family, which is the point: it looks like a scheme, not like the game ignoring you.
  • Zero chroma → separated by lightness alone. Painting a rainbow over a deliberately greyscale theme would be vandalism.

Text that survives its background. Every text role is held to WCAG AA against every surface it is drawn on — bg, bgPanel, bgRaised, bgLift — not just one of them, and correction tries both directions rather than assuming away-from-the-background is the way out. A mid-tone accent can be unreachable toward white and comfortable toward black. A colour is only touched when it really fails; a well-authored theme passes through untouched.

Surfaces have to stay on the background's side. Some shipped themes define only foreground, background and selection, and selection is often a near-white highlight meant to carry dark text. Used as a panel on a near-black theme it produces a surface no text colour can satisfy alongside the real background. Anything that far out of line with the theme's mode is derived from the background instead of used as-is.

tools/dev.sh themecheck grades every theme Omarchy ships and every theme you have installed and the fixtures in tools/themes/ (light, monochrome-light, greyscale) — 29 palettes on a stock machine. It prints the resolved mode, the contrast ratio and grade for each pair, the seven resolved hues, and the closest pair among them, and holds themes to the same threshold the code enforces, because a harness grading against a different bar is worse than no harness.

Layout

manifest.json     plugin manifest (overlay + bar-widget, keepLoaded)
Panel.qml         overlay entry point for omarchy-shell
BarWidget.qml     the car icon in the bar
shell.qml         standalone entry point (qs -p .)
game/
  Balance.qml     all config: cars, takes, waves, circuits. Data only
  Sim.qml         coordinator: wallet, clock, wiring
  Stats.qml       the run ledger: damage by car, kills by take
  EnemyManager.qml, TowerManager.qml, ProjectileManager.qml, WaveManager.qml
  Battlefield.qml, Quattro.qml, Creature.qml, EnemyChip.qml, Fx.qml, ...
  Theme.qml       live Omarchy palette: mode, WCAG contrast, hue separation
tools/dev.sh      dev harnesses (see below)

The managers hold no rendering code and run fine with no view layers attached, which is what lets the balance be tested headlessly.

Development

tools/dev.sh simtest       # headless mechanics + balance checks
tools/dev.sh viewtest      # view/entity parity and health bars
tools/dev.sh combatview    # real Game: combat health bars + inspector updates
tools/dev.sh preview 3     # play a circuit (0-4), screenshots to $SHOT
                           #   SCENARIO=rank|route|pause|over for those states
tools/dev.sh artcheck      # every car and creature, side by side
tools/dev.sh monotest      # each damage car played solo, to compare them
tools/dev.sh themecheck    # every installed theme + fixtures, graded
tools/dev.sh safetext      # untrusted file text cannot become rich text

You never need to reinstall. A symlink install points the plugins directory at this checkout, so the files the shell reads are always the files you just edited. Only the shell's in-memory copy goes stale.

Saving a file under ~/.config/omarchy/plugins/ normally hot-reloads it, but the watcher does not follow the symlink out to this directory, so after editing:

tools/reload.sh          # hot reload, verified; restarts the shell if needed
tools/reload.sh --soft   # hot reload only, never restart
tools/reload.sh --hard   # go straight to a shell restart

reload.sh stamps the sources into game/BUILD, reloads, then asks the running shell what it actually has via omarchy-shell shell call ... buildInfo. If the hot path did not take it restarts the shell and re-checks, so the command either prints the build you just wrote or fails loudly.

That check exists because a long-running shell can wedge its reload state, after which every rescanPlugins is a silent no-op and you end up debugging code the shell is not running. The same stamp is printed on the circuit picker, so you can always see which build is on screen.

Balance lives entirely in game/Balance.qml. Adding a take, retuning a car or drawing a new circuit never touches the managers.

Credits

Built for Omarchy. Weapon names and glyph effects are lifted from Omarchy's ttfx screensaver. Packaging follows the path Quattrolitaire laid out for native shell-plugin games.

The colour handling is shaped by Aether by bjarneo, the theme generator Omarchy ships for custom theme development. Aether is what taught this game which palettes it actually has to survive: its Monochromatic, Muted and Pastel extraction modes collapse the ANSI hues onto one, its light mode swaps the anchors out from under you, and its WCAG grader sets the bar themes are authored against. The hue separation and contrast rules above exist because Aether makes all three of those a normal thing for a user to be running, not an edge case.

MIT.