HN user

klibertp

4,952 karma

I'm a programmer and a programming language (both in theory and practice) geek. I advocate polyglot programming and have a site dedicated to it: https://klibert.pl/

Contact: The best way to contact me is to send an email to piotrklibert of fastmail.com domain. Or you can leave a comment here: https://klibert.pl/articles/contact.html or ping me on Discord: piotrklibert

Take a look at https://klibert.pl/articles/about.html for more info about me.

Posts10
Comments2,904
View on HN

building alternatives and nobody used them

Right. I kind of forgot that MUDs are supposed to be social. Seeing as the popularity of MUDs plummeted, I also changed my goals over time: I want to build a persistent, text-based (but with rich formatting support, including eventually GUI widgets) live programmable environment with network transparency and multiple concurrent sessions allowed. Since it's personal/local space first, and only allows others to "visit" (and even that is mostly for my own remote sessions), I don't need many users initially. If I was trying to build something that emphasizes the multi-user aspect, the choice of continuing one of the historical lineages would be much harder to avoid.

Secondly, existing "mainstream" languages are not appropriate, mostly. None of them or their runtimes are really based on a properly sandboxed persistent model.

You're right. And even if you broaden the search so much that "mainstream" is barely visible from the cut-off point, you're left with basically just Smalltalk. My current attempt is based on GToolkit distribution of Pharo Smalltalk - it does check some boxes, but it's class-based (not a big problem given the dynamism) and VERY single-user by design (quite a big problem, and thinking about eventual security/permissions/capabilities handling gives me a headache). Still, from all the prototypes I made over the years, this attempt went the farthest; I think I'll stick with it for a while longer.

Finally, I have all sorts of ideas on what I think is "better" than MOO

Thank you, I will definitely check it out :)

But the TLDR is that one of the arguments you pass to the system at start is basically a source directory of ("objdef") human readable / editable source files, and that gets compiled into a persistent binary database.

Thanks. That's exactly how a Smalltalk image is built, too, at least with the tooling I'm using (GT). You start an image, point it to a set of Git repos with Smalltalk code, which is then loaded into the image, classes are created, and methods get compiled. You then dump the new image. When you start it the next time, it already contains all the code, and you can change it inside the image at runtime, as much as you want. At some point, you export the changed code back to Git repos, and regenerate the image. Since the image contains all the live objects as well, you can close it, and all the state is restored when you reopen it.

Thanks for the Discord invite, I just joined. I'll go over the book and try to run mooR and mica first, and will reach out for help if needed.

For mica specifically: I'm not 100% sure, but I seem to remember you describing the idea behind it much earlier (here on HN) than this spring. I think it was in comments under a Python-Prolog bridge or something like that. Good to see you had the time and motivation to implement it :) I'll definitely give it a read!

Very cool! Why reimplement the "moo code", though? Is it "just" for backward compatibility, or did you determine that none of the existing languages can be easily modified to live in a MOO environment?

I started playing on an LP MUD in the late '90s. Over the past 20 years, I tried a few times to implement a similar environment, only with everyone being a "wizard". LPC was also a prototype-based OO language with multiple inheritance, and it supported live development, with code for objects stored in files and dynamically reloadable. I would always hit some kind of blocker, no matter the language I chose for the implementation. It looks like MOOs did (and do) what I wanted, but I just wasn't aware of their existence. :( Maybe if I used MOOs as a model, I'd get better results... but I'm not sure if I want to go all in on DB. I tried looking up how the code is loaded into the DB in the book (https://timbran.org/book), but couldn't find it. Is there an explanation somewhere of how MOOs are bootstrapped from sources, how you can maintain sources in files on disk, and how new definitions are pulled into the DB at runtime?

It tends to be. While it does use a GC, it has both bytecode and native AOT compilers. When compiled to native, it's closer in performance to C++ and Rust than to C# or Java. It's a reasonable middle ground in PL design that Nim also tries to occupy. In recent years, OCaml's runtime also shed the GIL, making it a good solution for parallelizable, CPU-bound tasks. As a language, OCaml is one of the most impressive ones (in terms of features and capabilities), yet it still provides strong, practical tooling, good performance, and access to low-level-ish features.

TL;DR: OCaml is usually going to be a bit slower than C++, but faster than Java, while rivaling Rust and Haskell in terms of expressive power.

Caveat: a lot of the power comes from the tooling. Last I checked, syntax extensions were external preprocessors, just with standardized APIs. And you need those extensions for things that are handled by (in-language) macros in Rust (like deriving string representation for a record). Without the tooling, OCaml can get a bit tedious/boilerplate-y. OTOH, after a period of instability and competing solutions, the tooling mostly settled down, and enabling the important parts is often a single line in the config. It's not a situation unique to OCaml, but if you look at the language docs only, you'll miss half of what OCaml development normally offers.

They all have failed.

That's not true. Most have failed, but those who used the right tool for the job - a rich, static type system in a functional language - did succeed. It's just that such type systems are rare, and even if nominally a type system is good enough, the required boilerplate might be uneconomical to maintain. Scala and F# are probably the only two languages that are mainstream-adjacent, at least, and have type systems expressive enough that using them to track escaping is not an absolute hassle. And they're both tiny in terms of the number of users.

In the end, we did settle on APIs that hide the escaping process (prepared statements, innerText vs. innerHTML, etc.) just because it's a) good enough; and b) possible to implement more or less uniformly across the TIOBE Top 20. That's good, but if you happen to use a language that's powerful enough, you probably also should track the escaped/unescaped status in the type system - it can be a cheap, additional safety net.

Sure. But you know, from personal experience, you can be a happy BASIC user writing simple games without knowing about the existence of arrays. It's a bit hard, but nothing copy & paste couldn't defeat. And while learning about `DIM` was quite a breakthrough for my 8 y.o. self, I'm not sure I'd welcome an article about it on HN's front page.

The same is true for iterator protocols: as noted in my other comment, they have existed since 1975 in almost the same form (with a few closely related variants). It's not easy to find a mainstream language that doesn't support them "since forever" (in "tech years"). They're just not news, no matter how you slice it.

I got curious and checked: CLU had iterators in 1975.

Another key feature of the CLU type system are iterators

It also had exception handling and tuple unpacking!

For instance, writing x,y := y,x would exchange values of x and y

(I'm joking, that's "just" parallel assignment, it just looks like what you do in modern Python with unpacking. And yes, it's all from 1975.)

Details: https://en.wikipedia.org/wiki/CLU_(programming_language)

It would be good, IMO, if people could come together and build out an open mobile platform not subject to SV hegemony

From what I understand (based on pretty basic research into using old smartphones (which I already have a full drawer of) as home appliances), the main problem is that device manufacturers only provide binary blobs for drivers and firmware, and they are not too happy to share them with non-Google parties. And it's non-trivial to handle those blobs, even if you get them (they weren't written for your tech stack, so you need infrastructure around them to make them useful).

Because building out AOSP and or just something forked/from scratch is... actually... accessible now in my opinion.

Starting such a project, and even getting to 0.0.1 release, is now simpler than any time in the past.

Getting from 0.0.1 to 1.0-alpha did not get any easier at all. The current AI requires both a great harness and a skilled operator to add meaningful code to a large project without going nuclear on code quality.

It'll be quite a few years until things like "make me a custom ROM with AOSP modified to do X" result in anything other than absolute tragedy.

We are subject to the fancies of the behemoths that exist to self perpetuate. Working around them and depending on them is demoralizing and not fun.

That's true, especially the "not fun" part. However, I expect the vast majority of users don't want any fun on their devices, aside from games (and even then, only with kernel-level anti-cheat). Normally, this is solved by companies offering one product for "casuals" and another for devs or power-users. This works, but breaks down for social things: a messaging app that won't run unless you buy a Pixel and flash a custom ROM is DOA as an app (it might function as a solution in case of people who are really desperate for the features the app provides, but that's probably too small a population to keep the project afloat).

Well, I really don't want to put the blame on anyone. Misbehaving apps on casual users' phones are a legitimate problem, and the OS is expected to mitigate that. Moreover, there are not many proven models for that, and they all entail compromising someone's convenience (either users', devs', or both). You can't have your cake and eat it, too.

In this specific case, though - especially given that the project had no iPhone version due to technological constraints - Android as a platform moving in that direction is probably the biggest reason why it became too hard to develop the project further. And the direction of Android development is set by BigTech, so you probably could justify calling them "the big obstacle".

It's important to note that the movement towards security-by-default is larger than just some subset of BigTech. It's how the whole industry tries to cope with computing becoming ubiquitous and trusted at the same time. It's Eternal September, but now the new users have banking apps on their phones. It's a hard problem, and every attempt to date has always resulted in users and developers losing some freedom. This OP just highlights the consequences of this movement for a particular project.

It just adds another layer of editing. For me, writing is always a multi-step process, even when many steps are condensed (in quick comments or IMs). First, dump the thoughts quickly in a rough form. Then settle the basic order of points and paragraphs. Fix the grammar. Simplify where needed. Fix the grammar again, then work on wording/phrases/metaphors if needed. Decide the final shape of the text, and do the last correctness pass. Before LLMs, I was done at this point. Now I need to also de-LLM the text before publishing/sending.

The problem is that the word "just" in the first sentence above does a lot of work. You want to get rid of even slightly LLM-adjacent phrases, just to be sure. And you have to do that pass always, for any writing that somebody else can read. It's less of a problem in larger texts, but the "de-LLM-ing tax" is pretty substantial on quick Slack messages. I think the time between typing the last dot and hitting Enter has easily doubled for me over the past 2 years. For now, most of the motivation is still just my care for the written word, but it is increasingly expected as a signal that I'm not letting an LLM write for me. I don't like where this is going, to say the least...

It's not. Modern Android is increasingly limiting what apps can do. It's a "code issue" in the sense that you can clone the Android sources, overhaul security and power management systems, and build your app to run on that. It'll work. It's doable. Would that be a solution for this project, though?

Try it. The AI will probably tell you that it's, of course, doable. You would have to start by making your own AOSP distribution and require an unlocked bootloader to even attempt to install it. You definitely can throw an AI agent at the problem, but a) it'll be significantly more than $200, no matter how you cut it; b) you'll end up with tens to hundreds of kloc of AI-generated code in a security-conscious context; and c) you can forget about having more than a handful of the most desperate users[1]. Both b) and c) are fatal for a project like this.

The locking down of the Android platform, IME, is a massive, decade-long process[2] with "full speed ahead" corporate backing. Even just a few years ago, you could maybe code around some of the restrictions (if supported by users going into settings and tapping some checkboxes); today it's impossible even with root. To get working "push notifications" outside of the official channel, you need to hack the support into the OS - or accept that you probably will get the notification, but it can be anytime from a few minutes to a quarter hour before your app receives it.

[1] In which case, making them use tens of thousands of AI-generated code "for security" is a clear moral hazard you probably don't want to walk into.

[2] I don't want to judge whether it's a move in the right direction or not - that's a separate matter. But it is happening.

TL;DR: if you want a general looping construct that works no matter the container type, you need an iterator protocol that types can opt into. This is a standard technique adopted by most programming languages that are not explicitly low-level, and has existed for the past 60[1] years. The OP (re)discovered it and thought it was worth blogging about.

Well, it is kind of interesting to see how the very basic programming building block (iteration) gets generalized without incurring syntactic costs. Whether it's worth a place on the HN front page is debatable, though.

[1] Too lazy to track the actual first implementation, but I'd be astonished if the concept wasn't well-known by the 70's.

but it needs to be repeated any time "git gud" is used as an excuse for Git's UX, which is always.

It's uncanny how a completely different community can have discussions this similar. I wonder if somebody wrote an article like "Git is Dark Souls of VCS, here's 10 reasons why". It could become a hit in both communities (poor potential author, would he even live through that?)

EDIT: I'm doomed. The moment I saw ChatGPT generate this: https://klibert.pl/statics/jolly_collaboration.png ("using GUI frontends is to Git veterans like using summons for Souls veterans") I knew I must write that post; the commonalities are just too funny to pass on :)

I absolutely love the images on the landing page. "Inappropriate use of blame, push and pull to resolve a conflict" is pure genius. Even the name had a lot of thought put into it (didn't know about "Giraffe language" before). Beautiful! :)

The backend itself is fine, it's just trying to use JJ as a Git frontend for projects developed on Github (among 20 other devs using git, where each of them uses git through different interface and/or in different style) that's currently not ideal :(

However, still needing to "drop down" to Git every now and again to get something done makes it feel very much like a convenient wrapper on top of Git rather than a new SCM.

Yeah, but I think that's fine. Git is a solid foundation; it's not wrong to try to build on it initially. It gets you a usable implementation faster. It is a pain point, though.

If Git adds commands to support the JJ workflow, it would be hard to justify having JJ installed any more.

Is that a danger? I don't see Git as a fast-moving target. And among all the "convenient wrappers on top of Git", JJ seems to have the design least tied to Git, so I think it has the highest chance of breaking out of the Git dependency in the future.

IME, the only real pain point with JJ is Git integration. I love it, it's great in almost everything it does. Formalizing all the DSLs and using them consistently throughout the config and CLI is exactly how filesets, revsets, and templating should be implemented. The focus on exposing concepts as interfaces (without "directly reflecting internals"[1] in the UI) is only as good as the concepts themselves, and in JJ's case, they are powerful. The problem is that they are too flexible, so Git interop relies heavily on convention and discipline... which is fatal. The fact that (almost?) all states of JJ are representable in Git makes it easy to mess up your remote, and then you need a very solid understanding of Git to fix it.

If you can give `jj` a try on a greenfield, single-developer project, you probably should. It feels exactly like Git felt compared to Subversion (when you focus on JJ's strengths and use it in favorable conditions - it is young, after all). Get acquainted with `jj` on its terms, and if things click for you, you'll be a bit better prepared for the bumpy road that is Git interop later on.

[1] https://news.ycombinator.com/item?id=48903054

Yeah. In my GToolkit setup, I started with giving it eval. At first, it tried to "rebuild the world" for each task, but we gradually settled on a set of images for specific tasks that can be rebuilt as a separate step and are otherwise cached. That gave us fast eval, and it kinda snowballed from there. I thought I would need to implement some IPC into a live image, but the startup is fast enough that it doesn't matter too much. The agent now has both the textual source (in Tonel) on disk and can easily query a live image via CLI.

What do you think about making the agent write type annotations? There are built-in forms in CL, and in Smalltalk, I settled for pragmas for now. They are not checked, but since I started using them, I think the rate of one- or two-shotting solutions has gone up.

how relevant it was to share the same programming experience across environments.

Right. If you view the language as just a part of a bigger "programming experience", you can do with a worse language with better tooling (pathological example: (lack of) namespaces in many Smalltalks to this day). If you focus on language design above all else, you end up with either something pretty on paper (but never implemented) or something practical and (kind of) elegant, but it'll take 40 years to get the tooling to where you want it (and still won't cover many important capabilities).

Also, it's easier to swap languages if you focus more on tooling. From what I read, many Lisp and Smalltalk IDEs/environments were (and some still are) polyglot. I sadly didn't have access to them at the time, but I imagine switching from Turbo Pascal to Turbo C++ was easier than going from Turbo C++ to Visual C++? That said, tooling tends to break if the language actively fights it, so it's not like PL design is completely irrelevant.

Which is why, given their linage, JVM and CLR are the closest big mindshare ecosystems that somehow still have traces of those features when using their IDEs and runtimes

Yes. The only problem is that those concepts/features become niche black magic (somewhat unavoidable for advanced features in "big mindshare ecosystem"). You can definitely compile Java classes dynamically at runtime, install them, and use them normally; however, outside of frameworks/IDEs/JVM languages, the ClassLoader is used mostly for loading static assets, and even simple reflection is viewed with suspicion.

Using Smalltalk or Lisp directly puts those powerful concepts "front and center", which a) is good for education; and more importantly, b) feels good when things click :) It could be an advantage for small groups of programmers, but from what I understand, it becomes a liability in large organizations. Pharo is especially bad at it, and GToolkit doesn't fix everything (they do a good job at providing tools, but don't want to maintain too large a patch set for Pharo, which is a good trade-off given their limited resources). But I'm replying to "Why Lisp (or When)", and in this context, Smalltalk is (IMO) the better direct alternative than JVM/CLR.

Depends very much on your harness and effective use of tools. For vibe-coding, stick to Python.

It can get good with the right setup. I made Codex work with GToolkit (similarly underrepresented, but technically impressive and a seemingly good fit for LLMs), but it took a lot of tweaking of the project structure, extensive instructions in AGENTS.md, and some custom skills (some borrowed from Gt4Llm, the GT's built-in model harness). Out of the box, it burned tokens and took way too long to implement even basic things.

YMMV - as with everything LLM-related - but I think without a similar setup, an agent instructed to write CL/Clj/Scheme/Racket will have the same issues. It might be better in an established, large project - but starting from an empty Git repo, I suspect you'll have to fight your way to productivity.

So why Lisp (or when)

[...] among its extensibility, its interactive environment, the REPL, and a lot of other features we haven’t touched yet. It is the combination of all of them that makes Lisp programming what it is.

Agreed. However, although the alternatives are few, they do exist. Today, I'd like to convince you (whether you're OP or a commenter) to give one of them a try. I'm talking about GToolkit[1]: Smalltalk/Pharo-based reimagining of Smalltalk as a productive environment for modern system design, analysis, and implementation. It's based on Pharo and its VM, but with GT-specific extensions and replacements, developed on GitHub in both Smalltalk and Rust.

I used both Common Lisp and Smalltalk over the years for some of my side projects. Technically, the environments are comparable: image-based, live, interactive development is central to both. Lisp is easier to fit into modern workflows: it's still just files on disk. If you ignore the REPL and treat the image as a bundler, you can have a Java- or Python-like development workflow. You can gradually adopt the more interactive ways of working with the codebase. Smalltalk was historically more of an "all or nothing" approach, but nowadays it supports Git-based workflows as first-class, with GToolkit providing additional tools directly. GT is tied to a single IDE, but that IDE is genuinely powerful, pragmatic, and easy to customize: the entire IDE is Smalltalk code[2] that lives in the image beside your code, so you can live-edit any part of it at any time.

The languages differ, most obviously in the object model (single inheritance and message sends in Smalltalk, and multiple inheritance with multimethods in CLOS), but I don't think one is strictly better than the other for the vast majority of code. You just need to structure your code differently. Both systems are very dynamic, so neither is like Java or C++. After quite a few projects in both, I am convinced that this difference doesn't matter.

Both languages have very simple syntax. Smalltalk doesn't have macros; instead, it has an extensible/replaceable compiler for method bodies. This capability is used, for example, for compiling grammars into parsers/lexers in SmaCC. It's not as convenient for control-flow abstraction as CL's macros, but Smalltalk tends to make them out of blocks (lexical closures with non-local exits) plus actions on thisContext. In practice, I never found either language too limiting in what I could express, and both have small, regular basic syntax.

TL;DR: Common Lisp and GToolkit Smalltalk offer comparable technical merits, with the most pronounced difference being GT's built-in, high-quality IDE, which open source Lisp lacks.

The IDE (and the capability to build cross-platform, but natively rendered GUI apps) is the major selling point of GToolkit, but the bigger reason to consider it is social. Lisp had a bit of a renaissance from 2005 to 2015, but ~it has since died down~ (EDIT: After checking a bit, it's more like there are similar levels of development now, but it's just varied, and I personally hit a few areas where nothing new happened for a while. Or in other words, the hopes of CL getting catapulted to mainstream by the wave of adoption didn't materialize, and the adoption seems to have largely plateaued, with a slight upward trend since then.) A lot of great things for Lisp appeared in that time: ASDF and Quicklisp being prime examples. Unfortunately, the momentum was lost, and since then, the rate of development has plummeted. It's still a solid proposition because of its stability, which means a lot of old code still works perfectly well, but it's a double-edged sword: it would take a hundred miracles in a row for CL to get a plausible M:N concurrency story, for example. This isn't true for Smalltalk, and doubly so.

There's a small but active community around Pharo, mostly academics and hobbyists (IME; and it's from a decade ago, so YMMV). Pharo itself is already a "Smalltalk-like" language, and it regularly gets features that are not in original Smalltalk (stateful traits and slots (reification of class and instance variables) are good examples). It continues to evolve, and each version brings notable improvements to the language, the VM, and the class library. In CL, since there are many implementations maintained by various groups and the standard is set in stone, adopting extensions to the standard is incredibly hard and time-consuming. You can, of course, commit to a single implementation, but there are only 2 or 3 implementations that are actively developed and trying to do something "new"; they are all experimental and incomplete. And while SBCL has quite a few nice features, its codebase was a bit hard for me to grok (read: days on end of banging my head against a wall, then giving up; I'm much more comfortable messing with Smalltalk bytecode compiler).

Secondly, GToolkit is driven by a group of coders[3] who have established a profitable consultancy. It's being developed around a single vision that the authors swear helps them achieve their projects' goals. Even if you're not sold on moldable development[4] as a methodology, GToolkit gives you all the tools you might need in a modern development and then adds tons of domain-specific tools and utilities that might be useful after just a bit of adjustment. The whole environment is built to make such adjustments as painless as possible, too.

The only problem I experience with GToolkit is the lack of structured, book-like documentation. The GToolkit book is closer to the PHP wiki or a bundle of tutorials than something like the Rust book. It's not that the docs aren't there - they often are, but finding them effectively is a bit challenging. I found that simply cloning all the repos that GT consists of (tens, if not hundreds, but there's automation for that) and pointing an agent to the directory with them is often enough to quickly find what I'm looking for (if the built-in Spotter fails and I'm too lazy to construct an in-image search).

TL;DR: GToolkit/Pharo move much faster than the CL world, and while the number of maintainers might be similar (honestly hard to estimate, though), the effort on the GT side is more centralized and, in my opinion, heads in a better direction. If you want an extensible, live, dynamic environment that shares many of Common Lisp's strengths, but is more polished, more actively maintained, and feels more modern, take a look at GToolkit.

(Disclaimer: no affiliation, just a programmer who likes to explore unpopular languages).

[1] https://gtoolkit.com/

[2] Some parts are implemented as Rust dynamic libraries and called from Smalltalk via FFI.

[3] https://feenk.com/

[4] https://moldabledevelopment.com/

Software Bonkers 15 days ago

Yesterday, Codex helped me resurrect a project[1] from 2013, where the code was published, but trapped inside an exotic format that can only be read by a single application. That application had ~14 breaking releases (rewrite everything!) over the past 13 years. Needless to say, no current version of the app can read the 2013 file, and no current system can run version 2.0 (it's now at version 14) of the app in 2026. Codex helped me create a containerized environment that pulled dependencies from that era, connected the container to my host GUI session, and successfully exported the code to normal-ish text files. It took half an hour (of Codex time, not mine) to check changelogs and try different version combinations. I was quite happy with the results.

Today, I wanted to capture screenshots every 5 seconds on a Windows machine. Codex created a dotNet project that compiled to a (self-contained!) bundle. It worked, of course, and I even ended up using it. The problem was that the bundle was 167 MB in size, held together by lots of XML config, and implemented the "RBGA-bitmap to PNG file" dumping from scratch; the whole thing was close to 1.5k lines of code. 30 LOC of PowerShell would be a perfectly acceptable solution here. I was... well, not impressed, and I would never show this solution to anyone, but it at least works and I didn't need to brush off my PS skills as a result, which is kind of good enough in this specific case.

As a programmer, I can immediately recognize when the LLM output is something nobody should ever see. It doesn't matter if nobody's going to see it anyway, I agree - yes, "one-user applications" fall in that category. However, when you mention <<"real" software>>, that changes the perspective. How would you know whether your software is "real"(-ish) or not? "Hey Claude, are we production ready enough right now?" That won't work.

TL;DR: requirements for code quality are hard to estimate, and code quality is something many professional programmers struggle to consistently recognize (not to mention achieve). Until the models get good enough to make good code a baseline, "real" software will remain hard to write, and better left to people who can recognize bad code quickly. That doesn't mean only professional programmers: plenty of hobbyist coders are "non-technical" in general. But at least for now, you still need to be a coder yourself to get consistently good results from the AI.

[1] https://pleiad.cl/research/software/gradualtalk

Software Bonkers 15 days ago

I think the end goal the author describes:

I can imagine soon-to-arrive interfaces where you just drag and drop components while narrating your desires with your voice, the models able to perform the “brainstorming,” “planning,” and “work” — operations that can take ten minutes or longer today — in mere seconds tomorrow.

Is impossible without ASI and more. This very vision failed to materialize in the past (4th-generation languages?) and still routinely fails, not only with models/agents, but also with humans and entire teams of humans on the other side. For a model to be that useful, ASI is the first enabling factor, but it also needs to develop mind-reading hardware and software and convince people that it's not breaching their privacy. I don't think this is going to happen. Not this millennium, at least.

There is a good case for prototypes, MVPs, and personal mods. I can imagine more and more users making use of the freedom to modify the open-source code. Technically, it was always possible, but for a normal user, it was not a realistic choice (learn to code (long and hard) or hire a programmer (expensive and inconvenient)). Even for me - a programmer by trade - fixing bugs in random pieces of software I might use (or not) once in a while was something I very rarely had the spare time and energy to do. The capabilities of the current crop of AI models/harnesses make this WAY easier: cheap on a subscription and requiring much less of my time. But that's for personal code modification. Pushing vibe-coded changes to anywhere outside of my machine is still something I wouldn't do, because I can see how mediocre the output code is. Unless the models can always write code as good as the top 20% of human-written code, their output will remain a liability. It's OK if I'm the only user; it's wrong to push such a problem to others. The issue here is that non-programmers cannot recognize when the code is good enough, which is why I used the word "always" - otherwise, pushing LLM code to others is a coin toss whether it's helpful or detrimental (for the project, the maintainers, and other users).

I don't think this is true. Computers are still computers, and code executes (mostly) deterministically. What computers struggle with is making sense of unclear inputs, precisely because of their determinism. OCR, speech-to-text, and computer vision all proved extremely limited when tackled purely deterministically. The whole "ML revolution" before LLMs was about recognizing that messy, noisy data can be interpreted with some accuracy using statistical methods. LLMs are the continuation of that: they are even harder to measure and completely impossible to prove, but they bring capabilities we have been unable to achieve any other way for decades. Should they be used to handle deterministic logic? No, obviously not - but they do enable computer systems to start working in contexts where they either couldn't at all before, or were just bad at them.

you're going to get worse output from them

Not completely true. Giving developers hardware that is too beefy is the main reason why so much software breaks down when run on users' machines, which are generally old, on spotty Internet connections, and RAM-starved. Devs just don't need to think about performance unless it's really asymptotically bad, while the users bear the full brunt of inefficiencies.

I came to appreciate typos, slightly ungrammatical sentences, and creative plays on common phrases. They are completely absent in LLM output without a specialized prompt (and they seem to struggle to make believable language mistakes even when prompted), so they can serve as a signal when judging whether the text I'm reading was written by a human.

As an aside, I observed my stance on proper English use changing in real time over the past 2-3 years. I used to be proud of writing clinically correct prose, and found mistakes in grammar and vocabulary grating. Now, I kind of welcome them and have stopped caring at all about committing such language crimes. I used to cringe when someone didn't capitalize the first words in their sentences - not anymore. I think we're years away from LLMs convincingly faking human-like mistakes (since all the work currently goes into avoiding them), so it's going to remain a useful signal for a while.

As you note, being constrained in where you can go at level 1 is not a bad thing in itself, but it is a trade-off. Yes, as much as I personally don't mind it, I see how some players/designers might find such a limitation unfit for their game or not working for their playstyle.

You can still work around this without resorting to level scaling. You can give players an exploration option that avoids fighting, but sacrifices something else (ie., you can explore as a ghost, but can't take any loot with you). You can give low-level players some kind of newbie buff that will keep them alive (but won't allow them to win). You could make the perception of the world level dependent. You could also just scale locations or regions once, so that if a player reaches it at a low level, it'll become easier, and if you get there at a high level, it'll become the de facto endgame area. You could also just not implement leveling at all: The Bard's Tale (remake) was a bit like this.

Leveling up is not fun if it doesn't have an impact on how your character interacts with the world. If you're going to use level scaling to make leveling a no-op, it's often better to just go the action-adventure route and not pretend the game is an RPG. Seriously: what's the point of pumping my Strength stat if Villager A always has enough vitality to offset any gains on my part? It's especially grating if it's the same village and the same villager, revisited 40 hours later, and it still takes 3/4 of your health in a hit.

I don't think naive level scaling (Oblivion/Skyrim-style) is the right solution for "let the player go anywhere they want from the start". It's certainly a solution, but not a good one. A more nuanced mix of story justification, natural movement limitations (distance, special skills needed to get somewhere), level/area design (make areas with harder enemies less likely to be visited first), some non-combat exploration options, etc. all seem like a better way of letting the player experience open-world freedom without taking away the RPG progression "from zero to hero" from them.

I played a lot of Might and Magic VII back in the day (a few years before Morrowind IIRC). It was a huge world and you could go anywhere after getting off the tutorial island. Some endgame areas were gated behind lengthy quests, some areas were inaccessible without reaching them "the hard way" for the first time, others were impossible to reach without special skills or items, and there were skills/spells (invisibility, fly) that allowed safe passage through high-level areas if needed. Taken as a whole, it gave you a lot of options in terms of where to go at any given time, protected you to some extent from getting insta-killed because you made a wrong turn, but didn't make exploration trivial or level progression pointless. When you give players freedom, you should also allow them to commit suicide by charging at a horde of dragons while underleveled and underprepared. Trying to make the whole world immediately accessible is the kind of handholding that is actually limiting in the long run, and takes away an important part of the RPG experience. I don't think there are many CRPGs outside of TES that use level scaling - personally, I only experienced one in Wizardry 8, and it was way less absurd than Oblivion (areas had level ranges - you won't get an endgame area mob to be level 1 if you happen to reach it at that level yourself, and Villager A in starting area won't ever get to level 20 if you revisit it later).

So, between some limited/partial scaling and all the other design choices you could make, using Oblivion-like level scaling to "make the world accessible" is just bad, lazy design to me.

I hate level scaling in games with passion. An argument in favor I heard is that it's supposed to make it easier for the player to go anywhere from the start. Maybe, but coming from classic CRPGs and some TRPG background, I just can't bring myself to understand that impulse: you see a world-ending dragon god destroying a castle, and you think it's a good idea to chase it to its lair... at level 1?? With a plank in hand and with a shirt as your armor??? It's absurd!

Then there's the "level up by doing" mechanic, which is a mixed bag. It can be done well, but in Oblivion, it only resulted in putting a book on the spacebar. Voila, my character literally jumps around and looks utterly idiotic, but Acrobatics levels rise.

Being unable to just go fight a dragon at level 1, and then progressing to routinely curbstomping dragons (Baldur's Gate 2 - solo Sorcerer run is my favorite example: "I see, you're resistant to magic. Let me introduce you to the Magic Sequencer with Lower Resistance x4. Time stop. Horrid wilting. Wish rest. Oh, I didn't need to, you're dead already.") by the end. It's not the only selling point of RPGs, and it can sometimes be omitted entirely (e.g., Disco Elysium). But if there is a classic leveling system, I expect it to work, from zero to hero. It's not as fun if the numbers going up don't translate into real changes in how your character interacts with the world.