You are browsing as a guest. Sign up (or log in) to start making projects!

jachym_fukal

@jachym_fukal

Joined June 14th, 2026

  • 21Devlogs
  • 5Projects
  • 2Ships
  • 30Votes
Open comments for this post

3h 3m 17s logged

Designing the PCB

Designed my first PCB today, a custom macropad built around a Seeeduino, EC11 rotary encoder, a 0.91” OLED display, and standard switches. Used the Adafruit Macropad RP2040 as a reference for layout and size, then adapted it to my components. First-time PCB design means a lot of time spent figuring out footprints, trace routing, and why KiCad was doing something unexpected, but it clicked eventually. Case design is next, and that might actually take longer since I haven’t locked in an aesthetic yet.

0
0
2
Open comments for this post

2h 13m 54s logged

It is done!

I wired up and coded the 4x4x4 cube today. Hit an immediate wall: the Arduino Nano only has 13 digital pins and I needed 20, so I added two shift registers to drive the columns. Once that was sorted, the code was almost a straight copy from the 2x2x2 prototype multiplexing logic scales cleanly to another dimension. It works!

0
0
3
Open comments for this post

24m logged

Soldering over 100 connections is so fun!

Because of the dimension of the led cube(4x4x4), I soldered over 100 connections. It toke about 4 hours, because it is one of my first times soldering, but I forgot to record a timelaps for it 😭.
At least it is finished. Now only need to wire it up and write the code.

1
0
6
Open comments for this post

1h 9m 3s logged

Prototyped a 2x2x2 LED cube on a breadboard

Today the first step toward the 4x4x4 matrix I’ve been wanting to build for a while, was made. Started by watching a video on 2D matrices to understand multiplexing, and adding the third dimension turned out to be surprisingly straightforward: each layer connects to ground through a transistor, each column connects to a power pin, and that setup lets you address LEDs individually or sweep the whole layer at once.
Getting the wiring clean enough on a breadboard that nothing shorted took a few tries, but the prototype is working.
Tomorrow I’m soldering the full 4x4x4 cube and writing the animation logic, hoping it doesn’t immediately go up in smoke.

0
0
1
Open comments for this post

33m 59s logged

Built a Nerf Russian Roulette game

One chamber fires, the rest don’t, and if you’re the unlucky one the gun rats you out with a loud active buzzer. The core problem was detecting whether a dart actually left the barrel: tried a light sensor/emitter pair across the nozzle first, but alignment was a nightmare and I scrapped it after it kept false-triggering. Ended up wrapping bare wire around the inside of the nozzle and foiling the dart tip, when it passes through, it bridges the circuit and sends a signal. Finding a wire routing that stayed put, didn’t short against itself, but was still close enough to make contact took about two hours of fiddling. Next step is to add a electric shocker, which has more dramatic effect, but I am still figuring out the best way to do that without overcomplicating the handle.

0
0
4
Ship

BigBrother is a 1984-themed Slack bot that turns your workspace into Oceania. It runs five modules:
Surveillance tracks message activity across channels and maintains a leaderboard of who's been most "active" (watched). Telescreen broadcasts Orwellian announcements to the workspace. Police runs sentiment analysis on messages and flags thoughtcrime — positivity is suspicious. Truth rewrites real news headlines in Party-approved doublespeak using NewsAPI + Groq. DM lets you talk directly to Big Brother, who responds in full authoritarian character via LLM.
Built with Node.js + Bolt.js over Hack Club Stardance. Big Brother is always watching — especially in #1984.

  • 15 devlogs
  • 25h
  • 15.87x multiplier
  • 395 Stardust
Try project → See source code →
Open comments for this post

34m 51s logged

Wrote the README and stopped the prerequisites file from lying about itself

The repo had no README and prerequisites.md was three stray lines (axios, node-cron, fs) that didn’t actually match what package.json declared or what main.js fails fast on at boot. Rewrote prerequisites.md as a pure library install reference — packages, versions, and one npm install command — then wrote a README that follows the structure the Stardance judges actually want: one-sentence description, hero image slot, a try-it link, quick start, features, local setup, and a “how it works” section that earns its keep by naming real decisions (Socket Mode over HTTP so it runs behind any network, self-rescheduling crons with re-rolling fire times, file-locked JSON as a tiny DB instead of SQLite, sequential Groq calls to dodge rate limits).

The friction was the Node version. There’s no engines field in package.json and no .nvmrc, so I didn’t want to just assert “Node 18+” without evidence. Grepping package-lock.json for "engines" turned up ~100 matches and the binding constraint is @slack/bolt‘s transitive deps all declaring "node": ">=18" — so that’s the honest floor, stated as such.

Also swapped the watched channel ID in Code/main.js from C0BBBCER55W to C09F6AAP1PF to match the workspace in the invite link I was handed. Caught one real issue while reviewing: the “Try it” link was a channel permalink (/archives/C09F6...), which a non-member hits a sign-in wall on, flagged it, left it in since it’s an internal members link.

0
0
5
Open comments for this post

1h 58m 11s logged

Made Big Brother stop lying about your loyalty score

The /bigbrother-loyalty command was silently corrupting its own scoreboard. Every call overwrote the user’s stored score with whatever the current calculation produced — which meant a citizen who hit 200 points last week would see their displayed rank drop to 50 after a quiet few days. The scoreboard was a ledger of “most recent” instead of “best ever”, which is the opposite of how a loyalty panopticon should work.

The fix in Code/Surveillance.js was small but mattered: scoreboard() now only writes when the new score strictly beats the stored high, and stamps an achievedAt ISO timestamp on each new record. While in there, I also wired /bigbrother-scoreboard to refresh the caller’s score before displaying, so running it actually updates your entry — previously it just read stale data. The refresh is wrapped in try/catch so a transient Slack or HuggingFace failure doesn’t break the leaderboard read.

That scoreboard bug was the tip of a much longer audit. The same session surfaced and fixed ~50 defects across the codebase: Surveillance.presenceCheck was returning undefined (not 0) on unknown presence values, producing NaN loyalty scores that then broke the sort; Telescreen.propaganda() would crash with TypeError if broadcasts.json was empty; Truth.getNews() called .map() on a potentially-missing articles array; concurrent readFile/writeFile pairs on the same JSON file were last-writer-wins. Added a 12-line per-file async mutex in Code/lock.js and wrapped every read-merge-write critical section. Also added .env validation at boot — previously a missing HF_TOKEN would surface as an undefined auth header 45 minutes into a Slack session instead of failing at startup.

0
0
4
Open comments for this post

1h 3m 33s logged

Added DM support: BigBrother can now talk back

Users can now DM the bot and get a response in character as BigBrother, powered by Llama 3.3 on Groq. The bot keeps a per-user conversation history (capped at 30 messages) so it actually remembers what you said earlier in the conversation. History lives in memory, so it resets on restart, intentional, didn’t feel worth the complexity of persisting it.
The interesting part was Slack’s permission system. Adding the im:history, im:read, and im:write scopes wasn’t enough on its own, you also need to separately subscribe to the message.im event under Event Subscriptions. The scopes let the bot act on DMs, the event subscription is what actually delivers them. Spent a while staring at logs with no “DM received” entries before finding that.

0
0
36
Open comments for this post

22m 38s logged

Replaced every console.log in the bot with a leveled logger.

The old setup was five files each printing things like “Message received!”, “Responding…”, and “Messages saved!” with no way to tell which file was talking or which run produced which line. Replaced it all with a single Code/Logger.js that adds timestamps, severity (debug/info/warn/error), a subsystem tag per file, and structured metadata. Errors now print a full stack trace on a second dimmed line instead of [object Object].
The visual difference is the whole point. Before, /bigbrother-loyalty produced four console.log lines with no correlation. After, it produces one timestamped line per stage — fetch, score, scoreboard write — and you can grep by subsystem or level.
The interesting bug: my first Logger.js had a guard that re-checked whether meta was undefined, but I also had a fallback that promoted a non-Error second argument into meta. The two branches collided and swallowed the error stack entirely. Caught it in a two-line test snippet before staging, would have been invisible until the first real API failure.
Also stopped logging the full Hugging Face response on every sentiment check. Now it only prints the per-label scores rounded to three decimals, same diagnostic value, way less noise.

0
0
2
Open comments for this post

3h 17m 15s logged

Two Minute Hate, now interactive

Built a /bigbrother-hate command for the Slack bot that escalates the existing 11:00 Two Minute Hate through 3 stages (Whisper → Roar → Frenzy) based on call count, with in-character stage-transition announcements and increasing-intensity bot posts.
The interesting part was the difficulty curve. Base thresholds are 5 and 15 calls, but they get multiplied each day by yesterday / 7-day-mean, clamped 0.5–2.0. So if Monday had 20 calls and Tuesday only 15, Wednesday’s threshold drops to ~62% of the base — easier to escalate on quiet days. First pass forgot to reset usageCount and stage on the daily recompute, so the bot stayed pinned at Frenzy forever after one good session. Caught it on the second test run.
Wired it to the existing 11:00–11:02 cron window so calls outside it get an in-character refusal. Also added hateLevel.json to .gitignore since it’s pure runtime state.
Next: DMing the bot, probably tackling that before adding more interactive bits.

0
0
1
Ship Changes requested

Built at ETH Hackathon Munich 2026 in 48 hours by @jachym_fukal and @srijan: a multi-UAV coordination algorithm where three autonomous drones explore and surveil an unknown 3D graph together. The hardest part was fixing oscillation — drones kept bouncing between the same two nodes forever. We solved it with a departure-tick penalty that discourages reversing course. Biggest win: 100% completion rate across all 6 test graphs with a total score of 1538.7. To test it, clone the repo and run `uv run run_eval.py --submission my_solution.py --graphs graphs/train.`

  • 1 devlog
  • 11h
Try project → See source code →
Open comments for this post

11h 24m logged

How We Taught Three UAVs to Explore the Unknown in 48 Hours — ETH Hackathon Munich

This weekend @srijan and I competed at the ETH Hackathon Munich, taking on the ATS GmbH challenge: coordinate three autonomous UAVs to explore and surveil an unknown 3D graph as efficiently as possible. The score is the total flight distance of the slowest drone — so balancing all three agents matters as much as raw speed.

What we built

We implemented a greedy frontier-based exploration algorithm where drones bid on uncovered nodes using a makespan-balanced auction. To cover the graph efficiently, we used k=4 hop sensor footprints and soft Voronoi territory partitioning so drones naturally split the space without stepping on each other’s toes.

The hard parts

The trickiest bug was oscillation — a drone would arrive at node B, making A look like the best frontier, then immediately go back to A, and repeat forever. We fixed this with a departure-tick penalty that decays over 5 ticks, discouraging drones from reversing course.
Performance was another challenge: naively running BFS for every frontier on every agent every tick was way too slow on graphs with 7000+ nodes. Caching coverage values once per tick made it viable.

Result

100% completion rate across all 6 training graphs, with a total suite score of 1538.7 — just above our 1500 target, but we’re happy with it as a first hackathon submission!

0
0
1
Open comments for this post

1h 0m 16s logged

The /bigbrother-loyalty command now doubles as a live leaderboard, every call re-ranks and displays the top 10 most loyal Party members. Plugging into the existing loyalty score pipeline meant the scoreboard itself was straightforward to build, but getting the Slack Block Kit formatting right for a clean ranked list took longer than expected. Ended up iterating through a few layouts before settling on one that doesn’t look terrible.

0
0
1
Open comments for this post

1h 26m 27s logged

Loyalty scoring now factors in message sentiment.

I added positive/negative sentiment saving by reusing the existing 7-day message cache logic, then wired the Surveillance module to read those sentiment files the same way it already reads other JSON, no new infrastructure needed. The score itself now reflects not just how active a user is, but whether they’re saying good or bad things about the Party.

0
0
4
Open comments for this post

1h 14m 22s logged

Stopped reloading 10 channels every time someone checks their loyalty score

The surveillance system was fetching all messages from all 10 channels on every single /bigbrother-loyalty call. That meant a ~50 second wait every time. Not great when Big Brother is supposed to feel instant and omniscient.
The fix: the bot now keeps a local JSON file of all messages. When someone requests their score, it only fetches messages newer than the newest one already saved, then deletes anything older than 7 days to keep the file lean. On top of that, a cron job rewrites the whole file once a day so it never drifts out of sync.
Score lookups that took a minute now finish in a couple seconds. The panopticon is finally fast enough to be scary.

0
0
4
Open comments for this post

58m 14s logged

Get more than one 1984 themed headline

The /bigbrother-news command could technically accept a count, but it always returned exactly one rewrite no matter what you passed.
First, rewriteHeadline was looping over the headlines array but always sending headline[0] to Groq, so it was rewriting the same headline count times. Second, the function returned an array but respond() was getting that array passed directly as text, which Slack rejects with a 500 because it expects a string. Fixed both, headline[i] in the loop, .join("\n") before responding. Also added a .replace(/^[""]|[""]$/g, "") strip because the model randomly decides whether to wrap its output in quotes or not and I’d rather it just didn’t.
The original code also only ever called rewriteHeadline with a single headline and had no loop at all, scaling it up to handle multiple rewrites sequentially meant restructuring the whole function.

0
0
2
Open comments for this post

1h 22m 16s logged

Added the Ministry of Truth module

Fetches real news headlines via NewsAPI, pipes them through Llama 3.3 on Groq, and rewrites them as state propaganda. The results are genuinely unsettling because they’re based on actual news. A Tesla autopilot crash became “Citizen Misalignment with Vehicle Guidance Systems Results in Regrettable Isolated Incident in Texas Residential Sector” which I think is funnier than anything I could have written myself.
The bug that got me: I passed the respond function itself as the text value instead of the actual result. Slack received an empty object, returned invalid_payload, and I spent way too long staring at the Groq response before noticing the slash command handler was the problem, not the API call.

0
0
2
Open comments for this post

1h 51m 26s logged

Built the Thought Police module for my 1984 themed Slack bot

Added a Thought Police module that watches messages in a specific channel, scores their sentiment using HuggingFace’s RoBERTa model, and fires a Big Brother-themed response if the message is negative or positive enough. The bot replies in-thread which feels way more unsettling than posting standalone, like it’s whispering directly at you.
Hit a bunch of friction getting here though. The Perspective API I originally planned to use got sunset, then the HuggingFace inference endpoint URL changed mid-build and started returning ENOTFOUND, turned out the old api-inference.huggingface.co domain was dead and needed to be swapped to router.huggingface.co. Then the response labels came back lowercase (negative instead of Negative) which broke the score parsing silently, the bot was running but returning undefined for everything.

0
0
6
Open comments for this post

1h 52m logged

Automated propaganda broadcasts for my 1984-themed Slack bot, random timing every day, plus a daily Two Minutes Hate at 11:00.

Imagine you are the head of Propaganda of an authoritarian state and you need to automate propaganda releases. Well, this is how I felt today after trying to automate this exact process. In the book 1984 propaganda is everywhere, so I replicated that. The hardest part for me was the writing of the propaganda. This is because I, as unexpected as it sounds, am not the head of Propaganda in an authoritarian state, so after around 10 minutes I asked my good friend Claude if he could write me some propaganda. Claude generated 50 different messages.

If anybody has some propaganda messages I could use just DM me on Slack or write it in the comments!

The next step for me is to become the head of police and automate the flagging of negative sentiments.

0
0
2
Loading more…

Followers

Loading…