[{"content":"\u0026ldquo;Don\u0026rsquo;t reinvent the wheel.\u0026rdquo;\nIt is the first piece of advice every developer gets. It is efficient, practical, and safe. It is also the reason why so many of us have no idea how our tools actually work.\nWe live in the era of the Black Box. We npm install complex logic, we send HTTP requests without knowing how a TCP handshake actually negotiates. We are becoming great at gluing things together, but we are forgetting how to build the glue.\nI\u0026rsquo;ve created this blog to write along the lessons I learn on this journey.\nThe Skeleton Philosophy I am not here to build production-ready software. I am here to build Skeleton Projects.\nThat means stripped-down, raw implementation of a complex piece of technology. It is the \u0026ldquo;bones\u0026rdquo; of the software, no flesh, skin, or make up. Explicitly not production-ready.\nThis started in 2018 when I wanted to understand game engines. Unity tutorials weren\u0026rsquo;t enough! I needed to go lower level. So I started working on a renderer, game loop and simulation from scratch. Next I wanted to understand interpreters, So I\u0026rsquo;ll write a parser that can do basic math, even if it crashes on a syntax error.\nWhat\u0026rsquo;s Coming I am currently lining up the first few victims for the chopping block:\nSkeleton Engine: A small library to build 2D games (Loop, Renderer, Asset Manager). Skeleton Interpreter: A language parser that can (barely) run code. Skeleton HTTP: A web server that speaks the raw text of the internet. This blog is my lab note collection. It won\u0026rsquo;t always be pretty, and the code definitely won\u0026rsquo;t be production-ready. But it will be fun and honest work.\n","permalink":"https://mendoza.gg/posts/why-build-skeleton-projects/","summary":"Abstraction is great for shipping, but terrible for learning. It\u0026rsquo;s time to actually reinvent the wheel.","title":"Why I'm Building Skeleton Projects"},{"content":"Finding the X4 I\u0026rsquo;ve been trying to build the habit of reading more. I started with a printed book, The Hobbit, dropped it after a while, then tried reading on my phone instead, except reading on a phone means the next app over is always some social media app, and I know exactly how that story ends. So I started looking for an actual e-reader.\nThat\u0026rsquo;s how I found the Xteink X4. Not from a review, but from a Reddit post where someone had hacked a Game Boy emulator onto one. Read books and hack on the hardware, that sold me immediately.\nDeciding to build After I ordered it, I had to wait for it to arrive, first to Miami and then to Honduras, so I had a lot of time to think about what I could actually do with it. I decided to make a chess game. I love the game, and I\u0026rsquo;ve built other chess related tools before (I\u0026rsquo;ll talk about Boardcaster later this year). At first I thought I could just load an existing engine onto the X4, but after some research I found out it\u0026rsquo;s a bare ESP32-C3 with a lot of limitations, so I decided to write everything myself, board, engine, and UI. I started reading up on how chess engines actually work, mostly on chessprogramming.org. Part of the pull, too, was the constraints themselves. I wanted to actually feel the limits of memory and low level C development, not just read about them.\nThe board I started with the engine itself, since I knew that would be the hardest part. The first thing they teach you about chess engines is that you need a board representation. I made mine a single array of 64 bytes, one per square, with an enum for the pieces, then started writing move generation. I had to make sure the engine could generate all legal moves, and then make sure it evaluated them correctly. The harder part was handling special moves like castling and en passant, there are a surprising number of edge cases to cover. I even got to use a bitfield in C for the first time, to store castling rights, a nice little optimization. Here\u0026rsquo;s what the state looks like:\ntypedef struct { uint8_t board[64]; uint8_t turn; int8_t en_passant_square; // DAMN, first time using bitfields in C, 1 int vs 4 bools uint8_t castling_rights; // bit 0: white kingside, bit 1: white queenside, bit // 2: black kingside, bit 3: black queenside uint8_t halfmove_clock; /* half-moves since last pawn move or capture; draw at 100 */ uint64_t zobrist_hash; uint8_t king_sq[2]; /* [COLOR_WHITE] and [COLOR_BLACK] */ } ChessState; The king_sq array is a small optimization to avoid searching the whole board for the king every time I need it. zobrist_hash is a hash of the board state that lets me quickly check if I\u0026rsquo;ve seen a position before, useful for detecting draws by repetition and for skipping re-evaluation of positions I\u0026rsquo;ve already scored.\nWatching it play itself The very first version of that struct didn\u0026rsquo;t have the Zobrist hash, the king square array, castling rights, or en passant tracking. Even so, I got the engine playing games against itself, and predictably it got stuck looping the same handful of moves back and forth. I stepped in and played against it myself, beat it easily, but I was happy it could get through a full game at all. Adding the missing pieces later fixed it for good. Now when I let it play itself, it actually plays the game out, no more circles, just a real checkmate at the end.\n=== Depth3 vs Depth3 (white d3, black d3) === 1. White b1c3 nodes=1273 1ms 1. Black b8c6 nodes=2126 0ms 2. White g1f3 nodes=2750 1ms 2. Black d7d5 nodes=3749 1ms 3. White d2d4 nodes=3263 1ms 3. Black e7e6 nodes=12215 2ms 4. White e2e4 nodes=6760 1ms 4. Black f8b4 nodes=9105 2ms 5. White f1d3 nodes=16102 4ms 5. Black d5e4 nodes=14694 4ms 6. White d3e4 nodes=15993 5ms 6. Black g8e7 nodes=52327 11ms 7. White e1g1 nodes=26110 7ms 7. Black e8g8 nodes=44004 8ms 8. White d1d3 nodes=25258 5ms 8. Black h7h6 nodes=41535 9ms 9. White e4h7+ nodes=27908 5ms 9. Black g8h8 nodes=5560 1ms 10. White c1d2 nodes=28649 5ms 10. Black f7f5 nodes=32424 5ms 11. White d4d5 nodes=49847 9ms 11. Black e6d5 nodes=32980 5ms 12. White f3d4 nodes=64038 10ms 12. Black h8h7 nodes=110081 18ms 13. White d4c6 nodes=21757 3ms 13. Black b7c6 nodes=4564 1ms 14. White a1d1 nodes=12144 1ms 14. Black d5d4 nodes=8996 1ms 15. White d2e3 nodes=24275 4ms 15. Black c6c5 nodes=59447 9ms 16. White a2a3 nodes=49879 8ms 16. Black b4c3 nodes=8385 1ms 17. White b2c3 nodes=8742 2ms 17. Black e7d5 nodes=5078 1ms 18. White c3d4 nodes=54036 9ms 18. Black c5d4 nodes=16125 2ms 19. White e3d4 nodes=14975 2ms 19. Black c8e6 nodes=26055 4ms 20. White d3a6 nodes=13975 2ms 20. Black d8c8 nodes=59239 10ms 21. White a6c6 nodes=12881 1ms 21. Black c8e8 nodes=13692 3ms 22. White c6b7 nodes=12746 1ms 22. Black e8a4 nodes=20925 4ms 23. White b7b2 nodes=211518 35ms 23. Black a8b8 nodes=11104 2ms 24. White b2c1 nodes=10212 2ms 24. Black f5f4 nodes=28716 4ms 25. White f1e1 nodes=17438 3ms 25. Black f8e8 nodes=27467 5ms 26. White c2c4 nodes=55536 9ms 26. Black e6g4 nodes=23093 3ms 27. White c4d5 nodes=7793 2ms 27. Black e8e1+ nodes=14527 2ms 28. White d1e1 nodes=1259 0ms 28. Black a4d4 nodes=19893 3ms 29. White h2h3 nodes=5975 1ms 29. Black g4f5 nodes=13754 2ms 30. White c1c7 nodes=26248 3ms 30. Black b8b2 nodes=27728 4ms 31. White e1f1 nodes=56927 10ms 31. Black f5d3 nodes=20834 3ms 32. White f1d1 nodes=10189 2ms 32. Black d4f2+ nodes=18864 1ms 33. White g1h1 nodes=244 1ms 33. Black f2g2+ nodes=2757 0ms Result: Checkmate after 66 plies (1688743 total nodes) a b c d e f g h 8 . . . . . . . . 7 ♙ . ♛ . . . ♙ ♔ 6 . . . . . . . ♙ 5 . . . ♟ . . . . 4 . . . . . ♙ . . 3 ♟ . . ♗ . . . ♟ 2 . ♖ . . . . ♕ . 1 . . . ♜ . . . ♚ a b c d e f g h Starting with letters I had a lot of fun with the screen. The first job was just making sure the pieces were readable at all, so I started with the simplest thing possible, the same letters each piece gets in FEN notation.\na b c d e f g h 8 r n b q k b n r 7 p p p p p p p p 6 . . . . . . . . 5 . . . . . . . . 4 . . . . . . . . 3 . . . . . . . . 2 P P P P P P P P 1 R N B Q K B N R a b c d e f g h Learning e-ink the hard way I hadn\u0026rsquo;t written any kind of sleep or refresh cycle yet, so one night I left a game running and went to bed. When I woke up, the board was burned into the screen. I tried refreshing it, but it was too late, I can still see it faintly when the screen goes white, though it\u0026rsquo;s not noticeable once a board is actually drawn on top. That\u0026rsquo;s how I learned e-ink needs to be refreshed regularly to avoid ghosting, and that the panel only has so many refreshes in it before it starts to degrade. Now I do a full refresh every 20 plies, and the device sleeps after 10 minutes of inactivity.\nGetting real piece art After a few games on the actual display, letters weren\u0026rsquo;t cutting it, I wanted real piece art. I settled on 48x48 pixel bitmaps, 288 bytes each. Originally I had just one bitmap per piece, but I wanted more precision in the drawings, so this is where I let Claude drive for a bit: I asked it to generate bitmaps from the Cburnett chess piece set, and it did. I still had to hand-adjust a few of them, but after a couple more iterations I had a full set I was happy with, and the pieces finally looked like real pieces.\nPlaying it for real Finally, I got to play a real game against the engine on the device itself. It\u0026rsquo;s genuinely weird at first, mainly because the X4 has no touchscreen, just four front buttons, a power button, and up/down buttons on the side. So before I could play anything I had to design a way to select a piece and then a square to move it to.\nOne of the smaller algorithms I needed was generating the list of squares a piece could reach. For sliding pieces (rooks, bishops, queens) I wrote that as a \u0026ldquo;raycast\u0026rdquo;: keep stepping in a direction until you hit the edge of the board or another piece. That ended up shaping the controls too, left and right pick a direction after selecting a piece, then up and down pick how far along that direction to move, for the sliders. Knights and pawns are simpler, they only get the direction options, no distance to scroll through.\nA real opponent Once the controls felt smooth, I was happy to find out the engine was actually a real challenge. I lost the first game and won the second, and I haven\u0026rsquo;t played it since, I don\u0026rsquo;t want to end up with a loss on the record. While writing this post I asked Claude if there was a quick way to estimate its Elo. It suggested cutechess-cli, a command line tool for running engine matches, but I haven\u0026rsquo;t set that up yet. So instead it wrote a small Python script that ran the engine directly against Stockfish (the Homebrew build).\nAfter 256 games (64 per level) against Stockfish at 300ms per move, Skeleton scored between 35% and 81% depending on the opponent\u0026rsquo;s strength, crossing even odds somewhere around Stockfish@1900-2100, for an estimated Elo of about 1860.\n┌─────────────────┬────────────────┬───────┬─────────────┐ │ Stockfish level │ Record (W-D-L) │ Score │ Implied Elo │ ├─────────────────┼────────────────┼───────┼─────────────┤ │ 1500 │ 49-6-9 │ 81.3% │ ~1755 │ ├─────────────────┼────────────────┼───────┼─────────────┤ │ 1700 │ 32-12-20 │ 59.4% │ ~1766 │ ├─────────────────┼────────────────┼───────┼─────────────┤ │ 1900 │ 27-15-22 │ 53.9% │ ~1927 │ ├─────────────────┼────────────────┼───────┼─────────────┤ │ 2100 │ 18-9-37 │ 35.2% │ ~1994 │ └─────────────────┴────────────────┴───────┴─────────────┘ Where it landed I still use the X4 for reading, but it mostly lives on my desk now so I can flash the game onto it and play a round for fun. It\u0026rsquo;s become a great conversation piece. More than anything it was just a fun project, a programming exercise that pushed me into things I\u0026rsquo;d never touched before. I\u0026rsquo;d never done hardware programming before this, and along the way I picked up a lot about C, chess engines, and e-ink displays.\nWhat\u0026rsquo;s next I\u0026rsquo;d absolutely do another project like this, I like chess adjacent code. I have another one already in progress called Boardcaster, a PGN file editor and viewer that exports video of games with a TTS voiceover reading out the move comments, rendering the board, arrows, and highlights as it goes. It\u0026rsquo;s basically a chess content creation tool. Once I get it out publicly I\u0026rsquo;d love to write about building it, and everything that went wrong along the way.\nI\u0026rsquo;m a full stack developer by trade, so getting back into C after college was its own kind of fun, and I learned a lot about low level programming, memory management, and optimization. I also went into this as a personal \u0026ldquo;can I still code without Claude\u0026rdquo; challenge. I think I passed, but I also came out of it with a different answer: Claude is a genuinely useful tool for learning and understanding things faster, and that\u0026rsquo;s a good thing. I\u0026rsquo;ll definitely keep using it going forward.\n","permalink":"https://mendoza.gg/posts/chess-on-an-x4/","summary":"Bought a hackable e-ink ereader to build a reading habit, ended up writing a full chess engine and UI for it in C instead.","title":"Chess on an X4"},{"content":"About Me I’m David Mendoza, Honduran, 27 years old. And I take complex systems apart just to see if I can put them back together.\nBy day, I\u0026rsquo;m a Senior Full Stack engineer. By night (and weekends), I build Skeleton Projects.\nThis blog is the home of Skeleton Projects, where I write raw, stripped-down implementations of the software we take for granted: Game Engines, Interpreters, HTTP Servers, Ray Tracers, etc. I don\u0026rsquo;t build them to replace the tools you use; I build them to expose how simple the logic underneath can be.\nMy workflow is Neovim on an ortholinear split keyboard. I\u0026rsquo;m usually calculating chess moves or troubleshooting sourdough fermentation on my freetime.\n","permalink":"https://mendoza.gg/about/","summary":"\u003ch1 id=\"about-me\"\u003eAbout Me\u003c/h1\u003e\n\u003cp\u003eI’m David Mendoza, Honduran, 27 years old.\nAnd I take complex systems apart just to see if I can put them back together.\u003c/p\u003e\n\u003cp\u003eBy day, I\u0026rsquo;m a Senior Full Stack engineer.\nBy night (and weekends), I build \u003cstrong\u003eSkeleton Projects\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eThis blog is the home of \u003cstrong\u003eSkeleton Projects\u003c/strong\u003e,\nwhere I write raw, stripped-down implementations of the software\nwe take for granted: Game Engines, Interpreters, HTTP Servers, Ray Tracers, etc.\nI don\u0026rsquo;t build them to replace the tools you use;\nI build them to expose how simple the logic underneath can be.\u003c/p\u003e","title":""}]