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

otzpt

@otzpt

Joined June 8th, 2026

  • 25Devlogs
  • 7Projects
  • 6Ships
  • 60Votes
@otzpt · 16 · Portugal 🇵🇹
dev @otzpt — into overclocking & system optimization
favourite language: raw C
Open comments for this post

4h 56m 51s logged

CaffeineOS - Devlog #2: apps and workspaces

After devlog #1 i took a long pause from caffeineOS and started working on other projects, after around 1 month i got back to work on caffeineOS

What’s new

  • workspaces - Added 3 real workspaces on the topbar where each on is its own “OS”(idk how to explain it if you’vs used window’s workspaces or hyprland or gnome or really anything with workspaces you probably know what im talking abt)
    with its own open apps for example if you can have calculator open in workspace 1 but 2 and 3 wont have anything open, also when you switch workspaces there is a litle animation

  • Topbar polish - Well i didn’t only add new features i also decided to polish some things, added a menu logo with it’s own about and shutdown buttons and fake system icons like battery, wifi and volume

  • Apps - Added 2 new apps Notes and calculator, Notes app it’s just a simple text box with zero persistence, and the calculator app it’s just a hand written no eval() app that uses pure logic with if and else's to do math

  • Extras - Passed around 1 to 2 hours relearning java script to make sure i still knew what i was doing

Next up

So i was thinking doing just some polishing and then making this ready for submit, i could do some extra things but webdevelepment was never really my thing, see you guys on the next devlog

1
0
84
Ship Changes requested

My aim was never been to create a useful replacement for ChatGPT; instead, I decided to understand what goes on inside a language model and so I created the transformer, the training loop, the tokenizer/data pipeline, and the retrieval layer myself.

It went through two phases:

  • 24M parameters trained on TinyStories as the starter experiment
  • 155M parameters trained on ~2B tokens of security documentation
  • Full-text retrieval using SQLite FTS5
    A custom training pipeline that includes the use of mixed precision, checkpointing, and cosine LR scheduling, etc.

The model is awful at actually answering questions; that’s basically the whole point.

While they are able to generate coherent text and the more extensive model can make use of the security documentation that has been retrieved, don’t expect anything like frontier-model level reasoning; it’s a small model designed to learn from existing models rather than to replace those which cost millions to train.

From this project I learned more regarding transformers, training, data, retrieval, and the actual situation with small models than I would have if I had just used an existing framework.

  • 5 devlogs
  • 11h
Try project → See source code →
Open comments for this post

1h 47m 49s logged

tiny-llm: KV-cache, a public API, and shipping the whole thing

Last devlog left off with the 100M model trained and RAG wired into chat.py, generating in ~50s per reply. This one covers making that fast, and getting the whole pipeline live on the internet.

Understanding KV-cache

Without a cache, every new token recalculates K/V for all tokens already processed, even though those values haven’t changed. The KV cache keeps them around and only computes K/V for the new token.

Three real changes to model.py:

CausalSelfAttention.forward now takes an optional kv_cache, concatenates new K/V onto it along the sequence dimension, and returns the updated cache.

The causal mask only applies when there’s no cache. With a cache, the new token’s query attends over the entire K/V (past + new), and since generation only ever feeds one new token at a time once a cache exists, there’s no future position to accidentally see. “No mask” is the correct causal mask in that case, not an approximation.

Position embeddings need to be absolute, not relative to the current call. With a cache, x is only the new tokens, so position 0 here is really past_length in the full sequence, pulled from the cached K’s sequence-length dimension.

Before relying on the cache, I added a correctness check: run the whole sequence normally, then one token at a time through the cache, compare logits with torch.allclose. They matched. Wanted to confirm correctness before worrying about performance.

Wiring it into generation

chat.py and backend.py now do a prefill (whole prompt through the model once, building the initial cache) followed by decode steps that only pass the newest token plus the accumulated cache, instead of re-encoding a sliding window every step.

Deploying: Nest, Vercel, and bugs that only show up live

Backend/model: FastAPI on Hack Club Nest (Stardance’s rules don’t allow Hugging Face for hosting), running as a systemd service, bound to 127.0.0.1 behind Caddy for TLS.

Frontend: static HTML/CSS/JS on Vercel, auto-deployed via GitHub Actions.

Real bugs surfaced by going live:

addMessage() never returned the div it created, so every successful reply crashed setting .textContent on undefined, landing in the catch block and showing an error even when the backend answered correctly. Live on the public demo before it got caught.

The rate limiter keyed on a spoofable query parameter instead of the real client IP. Fixed by reading X-Forwarded-For’s last hop, which only Caddy can set here.

No protection against concurrent requests on a 2-vCPU, 1.5GB box with a documented prior OOM kill. Queuing behind a lock still pushed memory to the ceiling, so it became a 503 (fail fast) instead.

A missing message field crashed inside search(). Now returns 400 first.

The model’s sanity-check block ran on every import, not just direct execution. Gated behind if __name__ == "__main__":.

The result

/generate went from ~50s to ~4.0s, a ~12.5x speedup, same model, same box, real public HTTPS endpoint. Verified live, not assumed.

What’s running now

A GPT trained from scratch, retrieval-augmented over real security docs, served with KV-cache inference behind a rate-limited public API. Every piece went through a review pass that caught real, live bugs before calling it done. The model’s output quality at 100M params is the physical ceiling of this scale, not a bug to fix, that was always the outcome of training something this size from nothing. Getting the whole stack this far, correctly, is the milestone.

0
0
4
Open comments for this post

1h 10m 10s logged

tiny-llm: the 100M model is trained, and RAG is wired in

The 100M parameter run is done.
976,562 steps on a 2B-token corpus, and chat.py now pulls retrieved passages before generating instead of relying on memorized weights.

The problem, and the fix

Started out wanting this model trained purely on security content (HackTricks, PayloadsAllTheThings, SecLists, GTFOBins, CheatSheetSeries, wstg).
All of that combined comes to about 6.73M tokens. Compute-optimal for a 100M model is around 2B tokens, real security wikis just don’t reach that scale, and no amount of repo-hunting was going to fix that 300x gap.

The solution i found was blend the 6.73M security tokens into a 2B-token corpus, topped up with FineWeb-Edu for general English coverage. The model gets real security-domain exposure plus enough general text to actually hold together grammatically, not by inventing security content that doesn’t exist, but by being upfront about what’s actually available.

The training run

d_model=768, n_heads=12, n_layer=12 4x the previous 24M config. Config changes were straightforward, getting a resumable 50-hour run right was the actual work:

  • Checkpoint pruning — early on, checkpoints/ silently grew to 561GB before a torch.save call failed with a disk-full error mid-write. Fixed by keeping only the 5 most recent checkpoints on every save, removing older ones automatically.
  • torch.compile ordering — resuming from a checkpoint failed with a state_dict key mismatch the first time, because model = torch.compile(model) was running before the resume logic loaded the weights. Compiled models get every parameter name prefixed with _orig_mod., so loading a plain checkpoint into a compiled model (or vice versa) breaks. Fixed by moving the resume block to before the torch.compile call — load into the “bare” model first, compile after.
  • Verified resume actually works — killed the process mid-run, restarted, confirmed it picked up exactly where it left off (same step, same loss) before trusting it for the full 50 hours unattended.

Final loss landed around 3.5–3.8 (train and val tracking closely) — expected for a corpus this heterogeneous (security docs + general web text) compared to the tighter 1.4–1.8 range on TinyStories.

Wiring in retrieval

A 100M model isn’t going to reliably “know” security facts from 6.73M tokens seen once inside a 2B-token corpus — so instead of hoping the weights memorized it, chat.py now retrieves first:

  1. User’s prompt goes to a local SQLite FTS5 search index built over the same repos/ content used in training
  2. Top-k matching passages come back with source, heading, and BM25 relevance score
  3. Those passages get prepended to the prompt as context before the model generates

Straightforward in concept, but ran into block_size=256 limits fast, retrieved passages plus the question can easily blow past what the model was trained to handle positionally. Fixed by truncating the encoded prompt to the last 256 tokens before generation, keeping the question and “Answer:” cue intact even if some of the retrieved context gets cut from the front.

What actually happens now

Asked “What is SQL injection?” — with retrieval wired in, the model opens with something closer to on-topic (“one of the most powerful tools available to attackers”) instead of pure noise, but drifts into plausible-sounding but fabricated details a few sentences in. That’s the honest result at this scale: retrieval gives it real information to start from, but staying faithful to that context for 100 generated tokens is past what 100M parameters can reliably do. Better than without retrieval, not a fix for the underlying scale limit.

0
0
9
Open comments for this post

1h 47m 25s logged

tiny-llm: 24M model trained, and it actually generates coherent stories

Picked up from the last devlog — train.py was working (fp16, grad clipping, val loss, checkpointing), but only tested on 2000 steps. This one covers finishing the training setup, the real 4-hour run, and getting the model talking through a CLI.

Cosine LR with warmup

Added a proper learning rate schedule before committing to a long run:

  • Warmup — LR ramps up linearly from 0 to the target (3e-4) over the first 2000 steps, instead of hitting the model with full LR while the weights are still random.
  • Cosine decay — after warmup, LR follows a cosine curve down toward 0 over the rest of training, so the model settles into finer adjustments by the end instead of taking large steps the whole way through.

Nothing exotic here — PyTorch doesn’t need a library for this, just a function that computes the right LR for the current step and gets applied to the optimizer’s param groups each iteration.

The real run

474M tokens (the full TinyStories corpus), which comes out to ~231,445 steps at batch_size=8, block_size=256. Ran for about 4 hours on the RTX 2080 Super:
step 0: train loss 11.01 val loss 10.62
step 231000: train loss 1.48 val loss 1.49
step 231400: train loss 1.82 val loss 1.69

Train and val loss tracked closely the entire run — no overfitting. Loss in the 1.4–1.8 range by the end, a solid result for a from-scratch 24M model on this corpus.

sample.py: watching it actually generate

Wrote a script to load a checkpoint and generate text autoregressively — feed in a prompt, predict the next token, append it, repeat. Had to fix two small but important bugs along the way:

  • torch.arange and torch.triu inside the model default to creating tensors on CPU even when everything else is on GPU — same device-mismatch class of bug as during training, fixed the same way (device=x.device).
  • Loading the checkpoint failed at first because torch.compile prefixes every parameter name with _orig_mod. — stripped it with a quick dict comprehension before calling load_state_dict.

First real generation, prompted with “once upon a time”:

once upon a time, there was a fish named Fin. Fin was very brave and always had a big challenge to do. One day, Fin asked his friend, Sally, “Will you marry me?” Sally said, “Yes, Fin! Let’s go!” Fin and Sally had a big adventure in the ocean…

Not perfect logic (fish swimming through an ocean “full of fish to eat” is a little off), but grammatically solid, consistent character names throughout, and a real narrative arc. Exactly what you’d expect from a well-trained 24M model on TinyStories.

chat.py: a quick interactive loop

Wrapped the generation code in a while True loop so the model loads once and takes prompts repeatedly instead of restarting for every single generation. Worth noting for anyone trying this themselves: this is a base model, not an instruction-tuned chat model — it doesn’t “answer” prompts, it just continues them as text. Say “hello” and it treats that as the opening of a story, not a greeting to respond to. That’s expected behavior at this stage, not a bug — instruction-following is a separate fine-tuning step for later.

What’s next

The full pipeline — prepare.py → train.py → sample.py/chat.py — is confirmed working end-to-end, from raw dataset to a model you can actually talk to (in the “continues your text” sense). Next up: scaling to the 100M-param run on a 2B-token corpus, this time blending real security-focused text (HackTricks, PayloadsAllTheThings, and others) with general web text, plus a retrieval index so the model can pull in relevant passages instead of relying purely on memorized weights.

0
0
12
Open comments for this post

1h 2m 41s logged

tiny-llm: train.py works, and the model is actually learning

model.py and prepare.py were done, but nothing had actually been trained yet. This devlog covers train.py and the first real smoke test on the 24M-parameters config.

Writing train.py

Wrote the training loop the same way as the model piece by piece, understanding each part before moving on:

  • get_batch — pulls random windows from the memmapped .bin files. x is a block of tokens, y is the same block shifted by one position (next-token prediction). Understanding why y is just x shifted by 1 was one of those small moments where next-token prediction actually clicked.
  • Mixed precision (fp16 + GradScaler) — my GPU (RTX 2080 Super, Turing) doesn’t have real bf16 tensor cores, so fp16 with a GradScaler is the right call. The scaler exists because fp16 has a much narrower range than fp32 — small gradients can underflow to zero and silently vanish. The scaler multiplies the loss up before backward() and unscales before step(), so nothing gets lost in the process.
  • Gradient clipping — caps the gradient norm at 1.0 before the optimizer step, to stop occasional exploding gradients from wrecking a good run.
  • Validation loss — evaluates on a held-out batch from val.bin every 100 steps, wrapped in model.eval() + @torch.no_grad(), so I can actually tell if the model is generalizing instead of just memorizing.
  • Checkpointing — saves model + optimizer state every 1000 steps. Training a real run for tens of hours without this would be a great way to lose everything to one crash.

Debugging session

Hit two device-mismatch errors early on — torch.arange and torch.triu both default to creating tensors on CPU, even when the rest of the model is on the GPU. Fixed by explicitly passing device=x.device wherever a new tensor gets created inside the model. Small bug, but a good reminder that PyTorch doesn’t infer device placement for you.

First real run

Ran the 24M-param smoke test (n_layer=6, n_head=6, n_embd=384, block_size=256) on TinyStories, ~470M tokens, for 2000 steps on my RTX 2080 Super:
step 0: train loss 11.01 val loss 10.62
step 1000: train loss 3.34 val loss 3.47
step 1900: train loss 2.89 val loss 3.10

Loss starts right around ln(50257) ≈ 10.8, which is exactly what you’d expect from a model guessing uniformly over the vocabulary before it’s learned anything. Train and val loss track each other closely the whole way down — no sign of overfitting, which is what I wanted to confirm before committing to a much longer run.

What’s next

The full pipeline is confirmed working end-to-end: prepare.py → get_batch → model.py → train.py, all on my own hardware. Next step is adding a cosine learning rate schedule with warmup, then moving on to the actual 100M-param run.

PS: train.py had some AI usage

0
0
21
Open comments for this post

3h 9m 16s logged

CaffeineOS — Devlog #1: Hyprland-style tiling WM

Ported the WebOS1 (CaffeineOS Lite) codebase into the base for WebOS2 — full CaffeineOS, Hyprland-inspired.

What’s new

  • Dwindle tiling engine (tiling.js) — windows auto-split the screen recursively like Hyprland’s dwindle layout
  • Drag-to-swap — grab a window, drop it on another, they swap positions in the tree
  • Snap zones — drag near the left/right/top edge, get a live preview, drop to snap
  • Multi-instance windows — can now open multiple Terminal/Explorer windows at once (previously capped at 1 per app)
  • Floating dock (macOS-style magnification on hover) + open-app indicators
  • Topbar (Waybar-inspired) — logo dropdown (About/Shutdown), centered clock, fake system status icons
  • App launcherCtrl+Space opens a rofi-style searchable app grid
  • Rebranded WebOS1 → CaffeineOS Lite → now just CaffeineOS
    Reused ~90% of the HTML/CSS/JS from WebOS1 (login, terminal, explorer logic) — just refactored for multi-instance support and removed the fixed taskbar in favor of a topbar+dock split.

Next up

More topbar polish (maybe real workspaces), then some actually-useful apps (notes, calculator) with zero persistence — no localStorage, no cookies, everything dies on refresh by design.
 

0
0
8
Open comments for this post

5h 5m 19s logged

tiny-llm: getting the data pipeline and model architecture in place

Before writing any model code, I needed a way to actually get training data into a usable format. prepare.py handles that: it streams a dataset from HuggingFace (starting with TinyStories, ~470M tokens), tokenizes it with the GPT-2 BPE tokenizer via tiktoken, and writes it out as flat uint16 arrays split into train.bin and val.bin.

A few details mattered here even though I didn’t write this part myself:

  • Tokens are stored as uint16 instead of the more obvious int32/int64 — GPT-2’s vocab is 50,257 tokens, which fits comfortably under 65,536, so uint16 cuts file size in half for free.
  • The split between train/val happens per document, not per token. Each story gets an end-of-text token appended, and documents are randomly assigned to train or val as a whole — so a story never gets cut in half across the train/val boundary and leaks information.
  • Files are written so they can be read later with np.memmap, meaning the corpus never has to fit in RAM — training just maps the file and reads slices from disk on demand.

With the data pipeline sorted, I moved on to the part I actually wanted to understand deeply: the model itself. That’s model.py, CausalSelfAttention, MLP, Block, and GPT, all written and shape-tested from scratch.

Having both pieces done means the two “boring but necessary” and “the actual point of the project” halves are finally connected: real tokenized text on one end, a transformer architecture ready to consume it on the other.

Next: train.py — wiring prepare.py’s output into model.py via memmap batching, cross-entropy loss, AdamW, and a training loop.

0
0
29
Ship

What is it?
Navegation System is an automatic robot navigation program in C. Given a maze, it uses BFS pathfinding to find the shortest route from the start to the destination, then animates the robot moving through it step by step.

Challenges:
The biggest challenge in this update was moving from a fixed 10×10 maze to a fully dynamic one, chosen and typed in by the user at runtime. That meant switching every data structure (map, visited, parent, queue) from fixed-size arrays to dynamically allocated memory with malloc, and handling edge cases like blocked start/destination points and mazes with no valid path. I also fixed a cross-platform bug where the program failed on Windows due to Unix-only functions (clear, usleep).

What am I proud of:
Getting the malloc-based 2D map working correctly, especially the parent matrix, which needed three levels of pointers to trace back the path. Also proud of adding proper validation instead of assuming the user always types a perfect map.

How to test:
Run the program, enter the number of rows and columns, then type the maze row by row using . for open paths and # for obstacles. The start is always the top-left corner and the destination is the bottom-right corner. Watch the robot find and animate its way through.

  • 2 devlogs
  • 8h
  • 11.28x multiplier
  • 86 Stardust
Try project → See source code →
Open comments for this post

46m 16s logged

C Navigation System — Devlog #4: Dynamic Map

The feedback from the certification reviewer included a clear suggestion for the next step: make the map dynamic, and possibly allow variable dimensions instead of keeping it fixed at 10×10. That’s what I focused on in this stage.

The first challenge was realizing that a normal C array, such as char lab[10][10], isn’t suitable for this. The size of a regular array has to be known at compile time, but in this case the dimensions are only known after the user enters them at runtime. The solution was to use dynamic memory allocation with malloc.

For a dynamic 2D map, this is done in two steps: first, allocate an array of pointers (one for each row), then allocate each row individually.

char **lab = malloc(rows * sizeof(char *));
for (int i = 0; i < rows; i++) {
    lab[i] = malloc(columns * sizeof(char));
}

The eh_obstaculo function, which previously had the entire maze hardcoded inside it, became much simpler. It now just receives the map as a parameter (char **lab) and checks the requested position.

int eh_obstaculo(int x, int y, char **lab) {
    return lab[x][y] == '#';
}

To populate the map, I use fgets to read each row entered by the user, rather than asking for one position at a time, which would have been much more tedious to use.

The part that took the most work wasn’t the map itself, but realizing that every other data structure used by the BFS (visited, parent, and queue) also depended on the old fixed TAM value. They all had to be converted to dynamically allocated structures based on the number of rows and columns. The parent array, for example, became an int *** because each cell stores a pair of coordinates (x, y) representing its parent, which adds another level of indirection.

In the end, the program asks the user for the number of rows and columns, reads the map line by line, runs the BFS using these dynamic structures, and finally frees all allocated memory with free, including the parent array, which has to be released from the innermost allocations outward because of its three levels of allocation.

The biggest lesson from this stage was realizing that making the map “dynamic” isn’t just about changing one variable. It means redesigning the entire chain of data structures that depended on the original fixed-size implementation.

0
0
7
Open comments for this post

10h 0m 21s logged

VOIDTUNE Devlog, 0.8.6 to 0.8.10
From “a lot of tweaks” to “only tweaks that earn their place.” Plus, the stutter hunt that turned out to be a bad USB cable.

0.8.6: Started with about 170 tweaks, categorized into SAFE, EXTREME, and NUCLEAR tiers, along with a Privacy tab and camera/mic blocks. They worked, but I didn’t have a clear idea of what actually helps.

0.8.7: Added a Windhawk-style Customization tab, featuring a mod grid and instant toggles. Tweaks now persist and verify the real system state at startup. Developer mode includes hidden DevTools like Probe, Console, and Tweak Builder. Fixed an empty Drivers page due to stderr CLIXML corrupting JSON parse. The first cull removed DPC-watchdog-off, HPET-off, Spectre-mitigation-off, the whole Nuclear tier, the Privacy tab, and camera/mic blocks.

0.8.8: The Void redesign introduced a Living Dashboard with an animated health ring, starfield, live CPU and RAM sparklines, and storage meters. Tweaks got reorganized into 14 color-coded sections. We established a real design system with violet to pink gradients, glow cards, and nebula ambiance.

0.8.9: Added Auto Game Boost; toggle it once, and it automatically pins fullscreen games to fast cores (P-cores/CCD-0), pushes background apps aside, disables EcoQoS throttling, and restores settings upon exit. Documentation is only for Win32. DevTools expanded to include Process Monitor, Registry Diff, Network toolkit, and a persistent Tweak Lab with share codes. Fixed issues with the Startup toggle, Services page reporting incorrect states, false “failed” tweaks, and removed base64 PowerShell due to top AV heuristic concerns.

0.8.10: Focused on quality over quantity. The mission became clear: improve FPS, UX, minimize processes, and use the least RAM without compromising stability.
Removed harmful tweaks like No Memory Compression, Force All Cores, GPU Preemption off, and C-state disables as they lowered FPS and stability.
Eliminated 13 ineffective tweaks as most Network settings were TCP-only myths, irrelevant for UDP games. Reduced Network tweaks from 15 to 5.
Removed 4 RAM-wasting tweaks that increased RAM only for negligible FPS gains.
Changed HAGS and GPU MSI to opt-in status as they caused stutter on some rigs.
Introduced real process-cutters: grouped svchost processes (~50 down to ~10), disabled Telemetry Tasks, Background Apps, Consumer Features, Block Driver Updates, and “Remove Promoted Junk” like Candy Crush.
Added new safety features: a reboot prompt, “Full Reset to Windows Defaults,” and fixed the “random folder opens at login” bug.
In summary, tweaks went from about 170 to about 152, with every remaining one justified.

The Great Stutter Hunt: Spent days tracking down system-wide stutter, dealing with audio pops, FPS dropping from 190 to 122, alt-tab lag, FiveM crashes, and RAM benchmarks dropping from 19 to 10 GB/s. It felt like a tweak issue. The clue was unplugging a faulty external USB SSD, which caused significant lag. The event log revealed it was throwing I/O retries and NTFS flush failures. A failing USB drive overloaded the I/O bus, causing storage delays that stalled the entire system, even without running games. Once unplugged and applying just SAFE tweaks, GTA V locked at 230-239 FPS with around 170 1% lows. The PC remained solid. The lesson confirmed that I should trust the tweaks while always being cautious about the I/O bus first.

Sidequest: The native C build found its purpose as VOIDTUNE One-Click. It’s 177 KB, has no dependencies, and you simply hit “Optimize Now” to get it done. This is for WinPE, servers, and older installs.

Next up: 0.9, which will mark a milestone for DevTools. We will graduate the power-user layer out of hidden Developer mode into the main product.

The best performance doesn’t come from the most tweaks. It comes from having the right settings and the humility to remove those that don’t contribute.

0
0
3
Ship

ZeroG-KIT is a Windows CLI toolkit that includes 20 tools divided into five categories: system cleanup, utilities, security, network, and text/data tools. It is built in Python and automatically gains admin access when launched, so no GUI is required.

One challenge was maintaining consistency across the 20 tools in five separate modules. Every tool that interacts with files, the network, or system calls needed solid error handling. This meant using try/except blocks to prevent crashes with bad input. During the project, I also reorganized the code from a single utilities.py into a proper module structure. It took time, but it made the codebase much cleaner.

I’m proud of the final scope. What began as a few basic tools has grown into a toolkit I actually use. It now includes a process killer, a firewall check, AES encryption, a LAN scanner, all in one place.

To test it, clone the repo, run pip install -r requirements.txt, and then run python main.py. It only works on Windows and will prompt for admin access on launch. Check out the network scanner, the encryption tool, or the process killer; those are the most interesting features.

Try project → See source code →
Open comments for this post

6h 48m 43s logged

C Navigation System — Devlog #3: Windows Compatibility

After submitting the project for certification I ran into a problem that hadn’t even crossed my mind: the program simply didn’t run on Windows. I got feedback from a reviewer with a video of the error, and the message was clear, “clear is not recognized as an internal or external command.”

The explanation is simple once you get it. I had written and tested the code only on my Linux environment, and used system("clear") to clear the screen between each frame of the animation. Turns out clear is a command that exists on Linux and Mac, but on Windows the console uses cls. I’d never thought about this because I’d never tested outside my own system.

The fix was to use a preprocessor directive, #ifdef _WIN32, which lets the compiler choose which code to include depending on the operating system the program is being compiled on. This way the clear() function decides on its own whether to call system("cls") or system("clear"), without me having to maintain two separate versions of the file.

void clear() {
#ifdef _WIN32
    system("cls");
#else
    system("clear");
#endif
}

While reviewing the code I realized there was a second problem waiting to happen. I was using usleep() to control the timing between animation frames, and that function is also exclusive to Unix-like systems, it doesn’t exist on Windows. It probably wouldn’t have thrown an error right there in the video, but it would have further down the line as soon as the robot started moving. I solved it the same way, creating my own esperar_ms() function that uses Sleep() on Windows (which works in milliseconds) and usleep() on Linux (which works in microseconds), and swapped out all the old calls for this new function.

void esperar_ms(int ms) {
#ifdef _WIN32
    Sleep(ms);
#else
    usleep(ms * 1000);
#endif
}

The annoying part wasn’t so much writing the fix, it was realizing I had to test both versions before requesting re-certification again. I compiled it again on Linux to make sure I hadn’t broken anything, and the program still worked fine, I just got some unused variable warnings that were already there before and have no impact on execution.

The lesson I’m taking from this is that testing only on your own operating system gives a false sense that the program “is ready.” Only an external test or review catches these things, because in my own environment I would never have seen this error myself.

0
0
3
Open comments for this post

2h 11m 33s logged

Three more tools went in, plus a structural change that’s probably the bigger story this time.
The regex tester is straightforward, take a pattern, take a text, run re.findall(), wrapped in a try/except to catch re.error for bad patterns instead of crashing. Nothing fancy but it’s the kind of utility that earns its keep.
Lorem ipsum generator was a chance to use random.choices() properly, a fixed word bank, pick k words at random with replacement, join with spaces. Simple, but it’s the first tool that leans on random instead of secrets.
Process killer is the one I’m happiest with. It pulls every running process via psutil.process_iter(), sorts by memory usage, and prints a clean top-30 table. A second function, kill_process(), takes a process name, loops through and terminates matches, with try/except for NoSuchProcess and AccessDenied so it doesn’t blow up on protected system processes. Eventually want a GUI version of this with a red kill button next to the list, but that’s a later project, not blocking this one.
The bigger change: split the single utilities.py into proper modules. utilities.py now holds system/image/QR/password/unit-convert/process tools, security.py has hashing, password checking, encryption, vuln scanning, and firewall checks, network.py has the network tools, and text_tools.py has base64, JSON, case conversion, regex, and lorem ipsum. main.py got restructured into category submenus (System, Utilities, Security, Network, Text & Data) instead of one flat list. Toolkit is sitting at 20 tools now and the codebase is a lot easier to navigate.
Only thing left before this ships: menu polish, colorama for color, cleaner separators, and a version number in the header.

0
0
2
Open comments for this post

9h 28m 16s logged

Three more tools implemented and the toolkit is now at 17.

Base64 encode/decode was the simplest one. Python has it built in with the base64 module, so it was mostly about building a clean submenu and making sure the output was readable instead of raw bytes. Same pattern as the encryption tool, encode() on the way in and decode() on the way out.

The JSON formatter took a bit more. It reads a file path from the user, opens it, parses the JSON, and prints it back with 4-space indentation. The important part was wrapping the whole thing in a try/except — if the file doesn’t exist or the JSON is malformed it gives a clear error message instead of crashing. Something I’ve been doing more naturally now without thinking about it.

Text case converter was probably the most fun. UPPER and lower are trivial, Title Case is one method call, but camelCase and snake_case required actually thinking through the logic, split by spaces, transform each word, join with different separators. Doing snake_case first made camelCase easier to see.

19 hours in. Still have regex tester, lorem ipsum, process killer, and menu polish left before this is ready to ship.

0
0
8
Open comments for this post

2h 3m 46s logged

The network section kept growing faster than I expected.

After the ping checker and IP viewer, I added a port checker. You provide a host and a port, and it tries to connect with a 3-second timeout. It tells you if the port is open or closed. It’s simple, but I actually use this. It’s much quicker than Googling “how to check if port X is open” every time. I had to wrap it in try/except because if the port is closed, the connection throws an exception and crashes everything. I learned that the hard way.

Building the IP scanner was the most satisfying part. It grabs your local IP and removes the last number to get the network prefix. Then, it pings every address from 1 to 255. The first version printed the full ping output for every single address, resulting in 255 walls of text. I fixed this by switching from os.system to os.popen, which allowed me to capture the output and show only the IPs that actually replied. I ran it on my network and discovered 5 devices. One of them I didn’t even know was connected.

Now, I have 15 tools total. The network section is done.

0
0
3
Ship

I built a C program that simulates a robot automatically finding its path through a 10x10 maze using BFS (Breadth-First Search), with a step-by-step terminal animation. The hardest part was learning BFS from scratch — I had never implemented a graph traversal algorithm before. The path reconstruction was also tricky since BFS gives you the path backwards, so I had to reverse it before animating. Download the binary for your OS from the Releases page and run it in your terminal!
(Notice: Might have problems clearing the screen on windows)

  • 2 devlogs
  • 9h
  • 6.43x multiplier
  • 61 Stardust
Try project → See source code →
Ship

I created CaffeineOS Lite, a WebOS that runs entirely in the browser. It features draggable windows, a functional terminal with commands like apt install, neofetch, and sudo. There’s also a file explorer with folder navigation and image preview, a login screen, a welcome window at startup, and a Start Menu with a Shut Down button. The design follows a caffeine theme and uses a dark Catppuccin color palette. Try it out; no password is needed. Just type any username and press Enter!

Try project → See source code →
Open comments for this post

3h 14m 37s logged

The security section is done, network tools are in, and the program now handles admin privileges on its own.The security section is done, network tools are in, and the program now handles admin privileges on its own.
For the network side I added two things. A ping checker that sends 4 packets to whatever host you type in, and an IP viewer that shows your local IP using Python’s socket module and your public IP by calling curl ifconfig.me. Both are things I actually use all the time and was tired of opening a separate terminal for.
The firewall assistant checks if all three Windows profiles are active and whether port 3389 is exposed. That last one matters — leaving Remote Desktop open is one of the most common security mistakes on Windows machines.
The self-elevation part was probably the most satisfying to figure out. Some tools need admin rights to work properly, and instead of making the user remember to right-click and run as admin, the script checks on startup and relaunches itself elevated if needed. My machine has auto-UAC so I don’t see the popup, but it works.
14 tools now. Ready to submit.

0
0
3
Loading more…

Followers

Loading…