Actually Open AI

Maybe we do have “ChatGPT at home,” now.

OpenAI recently released two actually-open LLMs, suitable for running on local, consumer-grade PC hardware. gpt-oss:20b and gpt-oss:120b appear to be among the most capable, efficient, and reliable local LLMs I’ve tested, so far. They’re no GPT-5-Thinking, of course, but even the 20b model has handled all the logic puzzles I’ve thrown at it, so far, including competently writing a C function to find the midpoint of a great-circle path anywhere on Earth.

Performance, at least for the 20b model, is quite good, considering the high quality of the responses. Inference runs at about 12-13 tokens per second, on a Core i9 system with an RTX4070 GPU. (128GB system RAM; ~16GB total used, so it basically fits in the 12GB VRAM.) The 120b model runs at 4-5 tokens per second, which is fair, considering it’s 6x larger. (I believe the 120b model uses a mixture-of-experts scheme, to limit the amount of the model that’s active at any one time.)

The ability to have local intelligent agents handling various tasks will open up a whole range of new, interesting projects. The next step is to try to get an idea of what kind of tasks various LLM model sizes can handle. qwen3:0.6b is really fast, but usually loses the plot when asked anything but a basic question. gpt-oss:120b is very capable, but communication is so slow that it might as well happen via Morse code.

Posted in Uncategorized | Leave a comment

Socket to ’em

The really cool, game-changing thing about the Internet is that it allows us to leverage the true N^2 value of large networks. Specifically, whenever a computer has a persistent Internet connection, it can exchange information with any other Internet-connected device at any time, quickly and easily.

When writing Internet-aware applications, such communications are often done with TCP or UDP sockets. These virtual connections represent a specific connection from one computer to another, through which data can be transferred.

What is needed for TCP/IP (UDP is similar but without verification steps) is a server program (to listen for incoming connections) and a client program (to contact a server with a request). As an easily-modified example, here is a server program in C that accepts a single byte, increments it (allowing rollover), and sends it back.

/* server.c : minimal TCP server that receives one uint8,
              applies myFunction() (here: ++ with wrap-around),
              and sends it back.                                */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 10000          /* listen port; change if you like */

/* Your pluggable transform ---------------------------------- */
static uint8_t myFunction(uint8_t x) {
    return x + 1;           /* uint8_t overflow wraps naturally */
}
/* ----------------------------------------------------------- */

int main(void){

    int srv_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (srv_fd < 0) { perror("socket"); exit(EXIT_FAILURE); }

    /* allow quick restart (otherwise TIME_WAIT delays) */
    int opt = 1;
    setsockopt(srv_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);

    struct sockaddr_in addr = {0};
    addr.sin_family      = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY); /* 0.0.0.0 */
    addr.sin_port        = htons(PORT);

    if (bind(srv_fd, (struct sockaddr *)&addr, sizeof addr) < 0) {
        perror("bind"); exit(EXIT_FAILURE);
    }
    if (listen(srv_fd, 1) < 0) { perror("listen"); exit(EXIT_FAILURE); }

    printf("Server listening on port %d …\n", PORT);

    for (;;) {
        struct sockaddr_in cli_addr;
        socklen_t cli_len = sizeof cli_addr;
        int cfd = accept(srv_fd, (struct sockaddr *)&cli_addr, &cli_len);
        if (cfd < 0) { perror("accept"); continue; }

        uint8_t in_byte;
        ssize_t n = recv(cfd, &in_byte, 1, 0);
        if (n == 1) {
            uint8_t out_byte = myFunction(in_byte);
            send(cfd, &out_byte, 1, 0);
            printf(" %u -> %u from %s\n",
                   in_byte, out_byte, inet_ntoa(cli_addr.sin_addr));
        }
        close(cfd);
    }
}

Here is a client program, which calls the server, sends a byte, and reports what it gets back.
(Change the IP address if the programs are not running on the same machine.)

/* client.c : minimal TCP client that sends a uint8
              and prints the transformed result.               */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 10000          /* must match server */
#define SERVER_IP "127.0.0.1" /* change to LAN IP if needed */

int main(int argc, char **argv){

    uint8_t value = 42;                          /* default test value */
    if (argc == 2) value = (uint8_t)atoi(argv[1]);

    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) { perror("socket"); exit(EXIT_FAILURE); }

    struct sockaddr_in srv = {0};
    srv.sin_family = AF_INET;
    srv.sin_port   = htons(PORT);
    if (inet_pton(AF_INET, SERVER_IP, &srv.sin_addr) != 1) {
        fprintf(stderr, "Bad server IP\n"); exit(EXIT_FAILURE);
    }

    if (connect(fd, (struct sockaddr *)&srv, sizeof srv) < 0) {
        perror("connect"); exit(EXIT_FAILURE);
    }

    if (send(fd, &value, 1, 0) != 1) { perror("send"); exit(EXIT_FAILURE); }

    uint8_t reply;
    if (recv(fd, &reply, 1, 0) != 1) { perror("recv"); exit(EXIT_FAILURE); }

    printf("Sent %u, got %u\n", value, reply);

    close(fd);
    return 0;
}

(Code provided by GPT-o3.)

Posted in C, Coding, Digital, Digital Citizenship, HOW-TO, Internet, Linux, Networking, System Administration | Leave a comment

Veeder-Root Counter

One favorite hobby of mine is visiting flea markets and antique stores, looking for inexpensive and weird pieces of technology, and figuring out what they do. The Mystery Telco Box is a good example; I knew it would be interesting but had no real idea what it did. (It’s a monophone power unit.)

Another gizmo bought roughly around the same time is a Veeder-Root device with a six-digit counter on it. The only control visible is a digit-reset wheel, which seemed to work correctly. It was clearly either a counter or timer — hard to tell which. (It could easily be based on a 60Hz AC motor, or could count pulses.)

The front of the device (after some testing)

The manufacturer’s plate, showing the model and expected voltage.
Still doesn’t directly say what it is. (I could look online, but that’s cheating…)

It wasn’t immediately obvious how to open it, other than removing the front cover plate. Loosening the four screws around the connection wires just seemed to loosen an internal piece, so I put them back in place.

Eventually, I noticed a drop of dried pitch in the middle of one side. Scraping it off revealed a flat-blade screw (the only mainstream choice worse than Phillips.) It came out with a minimum of fuss (for once), allowing the two halves of the cover to be removed. Pretty slick design, honestly.

With the covers removed, the inside is visible.

Now that the mechanism was visible, the device could be seen to be a counter. Two electromagnets work in tandem to pull a metal bar to advance the count. (The only problem was, manually pushing on this bar didn’t quite advance the count — the mechanism was out of adjustment.)

The back side, showing the escapement mechanism that advances the count.

A little experimentation revealed the problem — the bar was being pulled to the electromagnets correctly (good — I really didn’t want to re-wind the coils), but wasn’t being pushed back far enough by the spring to catch the next digit.

Veeder-Root helpfully provided a set screw for this function (and another for the backstop). By turning it almost to the end of its travel, I was able to get it to tension the spring enough to push the plate back in place, allowing a complete count. But we’re out of set screw at this point — if it fails again, it needs a new spring (or for this spring to be uncompressed a bit.)

The set screws. The top one tensions the spring, and is about as far in as it can go.

This counter will probably be a static museum display piece, so I don’t need it to be reliable. (Otherwise, I’d have replaced the spring.) But it’s working now, and (at least with a new spring) should keep on working for another eighty years or more.

Testing the repaired counter.
Posted in Mechanical, Reverse Engineering, Troubleshooting | Tagged , , , , , , | Leave a comment

World’s Worst BASIC?

I’ve heard people claim that the Timex-Sinclair 1000 / Sinclair ZX81 is the worst BASIC programming environment ever. That’s not even close. Borrowing the old Beatles drummer joke, the ZX81 isn’t even the least-capable computer made by Sinclair. The older ZX80 has half the memory and a smaller ROM. You kids were just spoiled by the ZX Spectrum having fancy features like color, a Chiclet keyboard, and sound.

And there’s a BASIC interpreter out there that makes the ZX80 look like a supercomputer.

A “Hello, World!” variant, in Atari BASIC Programming.
(You can see it running here, if the excitement won’t be too much.)

Back in 1979, people were designing all kinds of cool games for the Atari VCS (Atari 2600). This was a hastily-designed (but insanely popular and fun) video game system that was intended to be an upgraded version of Pong. It was designed to implement two player sprites, a missile sprite, static background graphics, and on-screen numeric scores. Famously, it didn’t even have a video buffer, so each line of video had to be drawn in real time by the program. (There’s a great book — Racing The Beam — that talks all about it.) It’s really a glorified, generalized Pong implementation. The tricks developers had to do in order to create the games I loved as kids don’t lose a bit of the magic when you start to understand how they’re done. Instead, you understand just how creative the developers had to be.

Despite the overall system being designed to handle games roughly as complex as Atari Combat (at best), creative programmers were able to use system features off-label to make the system do amazing things. The Atari graphics system provided for two player sprites, two scoreboards, and a “missile” sprite, in addition to a rudimentary background graphics system. (If you’re familiar with Atari Combat, that’s the sort of game that was envisioned.)

Warren Robinett’s “Adventure” was one of the first examples that really showed what the platform could do, despite all of its design limitations. By creative use of the background and sprites, and triggering the loading of different screens when the player moved offscreen, a multi-room Adventure was possible, with dragons, swords, chalices, magnets, bridges, and the world’s most annoying bat. (Seriously — Rhindle the Red Dragon is just misunderstood, but that thieving bat has to go!)

Adventure was one of two cartridges Robinett wrote while working at Atari. The other was BASIC Programming. This isn’t so much a practical IDE for creating programs as it is a proof that with sufficient creativity and determination, a talented programmer could bludgeon the 2600 into doing just about anything. Even the much-maligned Atari port of Pac-Man was an impressive feat of programming, given the platform. To actually implement a BASIC interpreter is straight-up mad scientist level stuff.

Still, given the environment, Atari 2600 BASIC is necessarily limited. While you can technically have up to 26 variables, you probably couldn’t use all of them at the same time, due to the 63-character memory limit for programs(!) Variables are two-digit unsigned BCD integers, storing values of 0-99. There’s support for graphics (moving white and red dots around a blue field) and sound (playing one of eight notes). Input is actually BASIC Programming’s strong point, since it is able to handle four channels of analog input — two channels each of horizontal and vertical. (Presumably, this is done with four paddle controllers.)

BASIC Programming for the Atari 2600 is probably the single worst BASIC implementation I’ve ever come across, in more than four decades of programming every BASIC implementation I could get my hands on, from the Timex-Sinclair 1000 to the Tandy PC-6 and Model 100 to the IBM PC to 1980s smartwatches programmable by a custom app.

But it’s also probably the most impressive BASIC implementation, too — and no doubt inspired lots more games that the 2600 really shouldn’t have been able to play.

Posted in BASIC, Coding, Nostalgia, Reviews, Toys | Leave a comment