An idle game that dissects its own manipulation mechanics while they are working on you.
Auf Deutsch: README.de.md — das Spiel selbst ist zweisprachig.
The whole game sits in index.html — embedded CSS, vanilla JavaScript, no
frameworks, no build tools, no external dependencies, no network requests.
Double-click the file and it runs; a server is not required.
Layer 1 — the idle game. A factory for attention, resource: impulses. A click button, six generators (Stimulus, Habit, Loop, Trigger, Ritual, System), prestige via synapses, offline progress up to eight hours.
Layer 2 — the leaflet. Thirty-one mechanics work on you first and get dissected afterwards.
Fundamentals: number rush, oven-glance (Zeigarnik), variable reward (Skinner), loss timer (Kahneman/Tversky), streak (commitment), FOMO event (scarcity), sunk cost.
Reward and chance: near-misses with a faked rate, a loss dressed up as a win, a machine with a pity counter, ratio schedule instead of interval schedule, a hit rate quietly adjusted behind your back.
Time and rhythm: energy with return appointments, login calendar with a reset, season with an expiry date, artificial waiting with a speed-up button, overflowing buffer.
Progress and ownership: pre-stamped loyalty card, goal gradient in the bars, a catalogue with gaps, achievement list, a plant you name yourself (IKEA effect).
Social: invented fellow players, a parasocial character, a neighbour with a constant lead, leaderboard on a rubber band.
Attention and money: premium currency, exaggerated feedback, blinking window title, endless history with no stopping cue, artificial maintenance outages (need frustration).
Every leaflet names its source, shows the player's real statistics and stays readable in the archive. Where the evidence is thin — the Zeigarnik memory advantage, the 66-day rule, phantom vibration, the parasocial purchase effect, screen time and wellbeing — the leaflet says so explicitly.
Invented numbers come from a deterministic pseudo-random generator, so the glass house panel can recompute them instead of merely asserting them. It even predicts what the next pull on the machine will yield, and names the actual hit rate while the gift button still displays the nominal one.
Clarity is the meta-currency for understanding. You do not buy production with it; you switch parts of the game off — up to glass-box mode and the closing credits.
The game exists in full in German and English — interface, all 31 leaflets including sources, store, mirror report, messages and window title. The starting language follows the browser language, the switch sits at the top right, and the choice is stored in the save. Number and date formats change with it (decimal comma against decimal point).
Technically the German original remains the template: the EN object is laid
over it. Where an entry is missing there, the German text still appears instead
of leaving a gap.
Everything lives exclusively in the browser's localStorage. There is no server
and no request to the outside. The Mirror tab evaluates this local data;
"delete save" removes it completely.
index.html?debug=1 blendet ein Dev-Panel ein: Zeitraffer ×60, Sprung um einen Tag,
Ressourcen-Cheats und ein manueller Auslöser für jede der 31 Mechaniken. Ohne den
Parameter ist davon nichts sichtbar.
index.html?debug=1 reveals a dev panel: time-lapse ×60, jump one day forward,
resource cheats and a manual trigger for each of the seven mechanics. Without
the parameter none of it is visible.
impressum.html carries the details required under § 5 ECG / § 25 MedienG, laid
out as on lichtenberger.dev. It is linked from the game's bottom bar and from the
article; in the container it is additionally reachable without an extension at
/impressum. The link in the game is relative so that it also works when the
file is opened by double-click.
For running on a server a container is included: nginx on Alpine, containing
only index.html, the legal notice and the article. The process runs as user
nginx on port 8080, the filesystem is read-only (read_only), only /tmp is
writable, all capabilities are dropped.
The delivered Content-Security-Policy allows 'unsafe-inline' for script and
style — both are in the file, after all — plus data: for the favicon drawn via
canvas, and nothing else. connect-src 'none' means: no fetch, no XHR, no
WebSocket. That turns the game's claim that no byte leaves the device from a
promise in the footer into a rule the browser enforces.
Routing via Traefik. docker-compose.yml publishes no port; it joins the
host's existing Traefik network and describes itself through labels. One router
serves both domains equally, www. is redirected per domain to the bare
address, HSTS sits at the TLS termination.
To try it without Traefik the bare container is enough:
docker build -t honest-idle-game .
docker run --rm -p 8080:8080 honest-idle-game
# http://localhost:8080 and http://localhost:8080/healthz/healthz answers with ok and is also used by the image's HEALTHCHECK.
The same host and the same Traefik as lichtenberger.dev.
.github/workflows/deploy.yml runs on every push to main and on manual
dispatch:
- build — builds the image and puts it in GHCR
(
ghcr.io/d1gl3/the-honest-idle-game, tagslatestandsha-<commit>). The built-inGITHUB_TOKENis enough for that. - deploy — copies
docker-compose.ymlto the host over SSH, logs in to GHCR there,docker compose pull,up -d. It then waits until the container reportshealthy; if that does not happen the run fails and shows the last log lines.
As long as DEPLOY_SSH_KEY is missing the second step is skipped cleanly
instead of failing red — the image still lands in GHCR.
.github/workflows/ci.yml runs on all other branches and in pull requests:
build the image, start the container, check every path (game, legal notice,
article, images, /healthz, 404), verify the security headers — explicitly
including connect-src 'none', so the game's promise cannot fall out of the
delivery unnoticed — and confirm the process is not running as root.
1. DNS — point all four names at the host:
honest-idle-game.de. A 217.154.81.233
www.honest-idle-game.de. A 217.154.81.233
honest-idle-game.com. A 217.154.81.233
www.honest-idle-game.com. A 217.154.81.233
2. Host — create the directory for the service; the user deploy and the
network traefik already exist from lichtenberger.dev:
sudo -u deploy mkdir -p /home/deploy/the-honest-idle-gameThe directory name has to match nothing — it ends up in DEPLOY_PATH and is
needed nowhere else. On the existing host it is still called
the-honnest-idle-game, from before the repository was renamed;
docs/OPEN-SOURCE.md explains how to move it safely, or
why you can leave it alone.
3. Secrets and variables — under Settings → Secrets and variables → Actions. The existing deploy key can be reused; it merely has to be stored once more in this repository, because secrets apply per repository:
| Name | Kind | Value |
|---|---|---|
DEPLOY_SSH_KEY |
Secret | private SSH key of the user deploy |
DEPLOY_HOST |
Secret or variable | address of the target host |
DEPLOY_USER |
Secret or variable | deploy |
DEPLOY_PATH |
Secret or variable | /home/deploy/the-honest-idle-game |
DEPLOY_PORT |
Secret or variable | (optional) SSH port, if not 22 |
All of it applies per repository. If one of the four mandatory fields is
missing, the workflow skips the deploy with a notice naming the missing one —
the image still lands in GHCR. If sshd on the host is not on 22, DEPLOY_PORT
has to be set here specifically, even if another repository already delivers to
the same host successfully — otherwise scp aborts with
dial tcp …:22: i/o timeout. The workflow therefore checks reachability up
front and says so clearly instead of running into a timeout.
The four are read as ${{ secrets.X || vars.X }}; a secret that is set wins. As
a secret they are masked in the Actions logs, as a variable they stand there in
clear text — and the logs of a public repository can be read by anyone. Which
value belongs where, and why DEPLOY_USER should specifically not be a
secret, is in docs/OPEN-SOURCE.md.
Either way they are gone from the file, so that a public repository does not ship a finished map of the server and a fork does not accidentally run against someone else's infrastructure.
The GHCR package is pulled during deploy with the run's
GITHUB_TOKENand therefore works even while it is private.
blog/the-honest-idle-game.html describes the project from a media-psychology
perspective: all 31 effects explained briefly, the fact-check of the sources and
a verified bibliography. It is in German.
https://buymeacoffee.com/d1gl3
Voluntary and without consequence. There is no currency for it in the game, no advantage, no badge and no mention in the credits — a game that dissects monetisation should not introduce it through the back door. The link sits in the bottom bar next to the legal notice and the source code, deliberately quiet: no graphic, no blinking, no appearance after the tenth minute.
It also changes nothing about the game's promise. A link sends nothing; only the
click opens a new page. Referrer-Policy: no-referrer ensures buymeacoffee.com
does not learn where the visit came from, and the CSP stays untouched —
connect-src 'none' still applies, no badge and no script is loaded from there.
Licence: MIT. Rules for contributions: CONTRIBUTING.md. Security reports: SECURITY.md.
What has to be configured in the repository for opening it to be safe is in docs/OPEN-SOURCE.md — visibility, default branch, token permissions, fork pull requests, secret scanning. That document is in German.