davidyat.es https://davidyat.es/ Recent content on davidyat.es Hugo -- gohugo.io en-gb David Yates David Yates © 2024 David Yates Sat, 17 Feb 2024 20:40:25 +0200 Print stylesheets https://davidyat.es/2024/02/17/print-stylesheets/ Sat, 17 Feb 2024 12:59:57 +0200 https://davidyat.es/2024/02/17/print-stylesheets/

If you’re reading this on a device with a keyboard, press Ctrl+P now. On most browsers and systems, that should bring up a Print Preview dialogue for printing this webpage. You should notice quite a few differences between how this blog post appears in your browser and how it appears on the page. For one thing, the text is split into two columns, and for another, many parts of the page – the site header, footer and several buttons – have disappeared. What is this wizardry, you may ask? As the title of this post indicates, it’s all about print stylesheets.

Media queries and the promise of @page

Media queries have been an important tool for building responsive CSS since their introduction with CSS 3 in 2012. Many of their original uses have since been superseded by natively responsive constructs such as Flexbox, but they’re still the best way to make specific and potentially quite radical changes in layout between different screen sizes. For example, I use the rule below to display description lists in two columns on larger displays, as opposed to their browser-default vertical appearance:

@media (min-width: 600px) {
  dd { grid-column: 2; }
}

I’ve also used media queries (and CSS variables) to create a dark mode for this site without the need for a day/night button and all the extra JS that would go with that:

@media (prefers-color-scheme: dark) {
  /* ... */
}

Both of the above snippets use media queries without a media type, but perhaps you’ve seen directives that look like this before:

@media screen and (min-width: 600px) {
  /* ... */
}

As it turns out, media queries are a modern extension to media types, which was first introduced in CSS 2.1. Before media queries, all you could do was write rules like this:

@media screen {
  body {
    color: black;
    background-color: white;
  }
}

Or this:

@media print {
  h1 {
    page-break-before: always;
  }
}

Wait a minute, page break? In CSS? Yes – the purpose of media types is to apply different styles for different media, including screens, printed pages and more1. Here’s an Eric Meyer blog post from January 2000 on writing stylesheet rules for print so that you don’t have to maintain a separate printer-friendly version of each page on your site (remember those?).

Print stylesheets have been with us since the early days of the web, but they’ve never been a very well-known or publicised feature of CSS. Apart from @media print and paged media properties dealing with page breaks, the other key element for creating a print stylesheet is @page, an at-rule for controlling aspects of individual printed pages.

When I was putting together this site’s footnote previews, I came across this Smashing Magazine article, which made it seem as though I might be able to implement per-page footnotes for printed versions of my site. However, a bit of experimenting followed by a more careful read of the article revealed that the majority of @page’s proposed features have not been implemented in any mainstream browser. As of now, you can configure how big your page is, what its margins should be, and whether it ought to be portrait or landscape, and you can also do different things with left and right pages. The footnote rules and the margin rules intended for creating page headers and footers do nothing.

So, after excitedly implementing a bunch of print stylesheet rules, I abandoned my attempt at full printer-friendliness and forgot about the subject for the next three years.

Enter Paged.js

Recently, a project unrelated to this website gave me cause to think about this print stylesheet stuff again. While browsers don’t support most of this functionality, it’s a defined specification that has been implemented by several libraries and paid services for turning HTML into PDFs and books. The website print-css.rocks provides a wealth of information on the subject as well as a handy index of features showing which implementation supports what. There’s also a companion playground site that lets you test out the different implementations.

As far as free-to-use implementations go, Paged.js appears to be the most fully featured and well documented. It can be used as a polyfill script or through the command line. The polyfill script converts the page where it’s injected into an on-screen simulation of a printed document – you end up with your entire webpage in a tiny corner with scrollbars and page separators. You can then save the page as a PDF (or print it directly) using Ctrl+P, and this time the preview will show all those nice @page features.

My preferred method, though, is to use the CLI, which converts any HTML file to a PDF in one step:

pagedjs-cli input.html -o output.pdf

Under the hood, this injects the Paged.js polyfill script into input.html’s <head>, serves it to a headless Chromium and prints to PDF. Note that input.html should not already be using the polyfill – this causes crazy things to happen.

For the project I was working on, my pre-Paged.js print stylesheet had:

  • A couple of page-break-before rules for specific heading elements
  • @page size and margins
  • various @media print rules that followed the opposite of responsive design principles – font-size specified in pt rather than em and sizes of elements in fixed mm and cm. It’s rather freeing to write CSS for a page that you know won’t ever change in width or height.

From there, I delved into Paged.js-specific features such as page headers and footers, and a table of contents complete with page numbers.

I also had to deal with a few bugs (mostly of my own making). Here’s what I learnt:

  1. Writing CSS for a JavaScript polyfill provided some harsh reminders about how forgiving browsers are in comparison. Leave the second colon off a pseudo-element (e.g. h1:before instead of h1::before) and your browser will understand what you mean and apply the rule, but Paged.js won’t. Similarly, the browser will let you increment a counter in a pseudo-element, whereas Paged.js will not.
  2. MDN will tell you that page-break-after|before|inside is deprecated and you should use break-after|before|inside instead, but Paged.js still very much expects that preceding page-.
  3. There are many small differences between how a given browser will render a page for printing and how Paged.js will do it, beyond the additional feature support. For example, Chrome is a lot better at printing tables – it will automatically repeat table headers at the top of new pages for long-running tables,2 and is less prone to cutting off rows.3
  4. There are some non-obvious things Paged.js doesn’t support, such as CSS Grid, and some things it supports that Chrome doesn’t, such as element().4
  5. The most maddening thing about working with Paged.js is dealing with multi-page tables.5 At the time I’m writing this, the overflow detection is quite buggy, and a table with too much padding will just lose rows off the end of a page. It will still continue on the next page, though, so you’ll end up with, say, rows 1-8 on the first page and rows 11-14 on the second, with no sign of the missing two in the middle. In the end, I gave up on vertical padding in table cells beyond a pixel or two.6
  6. The second most maddening thing about working with Paged.js is dealing with multi-page codeblocks (<pre>). I had to set display: inline and then do all kinds of weird things to get the background colour back before they would split normally across pages like they do in Chrome’s print preview.
  7. The differences between Paged.js and browser printing mean that, at some point, you’re likely to end up with three sets of stylesheet rules: what to show on screen, what to show in browser print and what to show in Paged.js. And by the very nature of what Paged.js does – simulating @media print on @media screen – there’s no way to differentiate between those last two, so we can say goodbye to the dream of a single stylesheet.

Final thoughts

My goal with making a print stylesheet for this site was to have some neat surprises for anyone who attempted to print a page from it. As of today, most of the neat surprises I would have liked to include would only be displayed through a third-party library, which would spoil them. Paged.js and its competitors are very much dev-oriented internal tools that wouldn’t make sense to implement here.

That said, this has been a very useful discovery for other projects, and I’m heartened to see such actively developed and feature-rich libraries for producing print media with HTML. I’ve done my time in the LaTeX mines and it is with a wealth of experience that I say I’d rather use HTML and CSS for my document finagling. So I’m glad there’s a decent way to do that for print (and fake print).7

Useful resources


  1. All media types except print, screen and all have since been deprecated↩︎

  2. To do this in Paged.js, you need to use some JavaScript code from an issue on their Gitlab. To run the code with pagedjs-cli, save it to a file and use the flag --additional-script repeatingTableHeaders.js↩︎

  3. Both Chrome and Paged.js will split one table cell across multiple pages sometimes though, even with break-inside: avoid↩︎

  4. Firefox remains the only major browser to support this crazy CSS feature (which is very useful in Paged.js for creating page headers and footers). ↩︎

  5. You could probably say the same thing about LaTeX and many other document formatting systems. Tables, man. ↩︎

  6. There’s a massive PR of pagination fixes open right now, so hopefully this gets resolved for the next release. ↩︎

  7. “Very well then I contradict myself…” ↩︎

Reply via email

]]>
Adventures in latent space https://davidyat.es/2023/10/19/latent-space/ Thu, 19 Oct 2023 11:27:05 +0200 https://davidyat.es/2023/10/19/latent-space/

I first wrote about Stable Diffusion last August, shortly after its initial public release, when Stability AI transformed LLM-based image generation from a black-box gimmick behind a paywall into something that could be used and built on by anyone with a Github account. In the intervening year, that’s exactly what’s happened.

Even at the outset, Stable Diffusion gave budding synthographers a wealth of tools not available for DALL-E 2 or Midjourney. Being able to control settings like randomness seeds, samplers, step counts, resolution and CFG scale, plus seemingly endless possibilities of both the text and image prompts were, it appeared, a recipe for creating just about anything. By altering text for the same seed, or running multiple iterations of img2img, the synthographer could gradually nudge an image with potential until it matched their vision. But it didn’t stop there. Updates and innovations have come so thick and fast that my every previous attempt at writing this post (for over a year) was undermined by some new advance or technique that demanded inclusion.

I won’t pretend to be comprehensive here, but I’ll try to cover major advances and useful techniques pioneered over the last year below. We’ll look at three aspects of image generation: text prompting, models, and image prompting.

Text prompting

The first new thing that got big after my last post was the negative prompt, i.e. an additional prompt specifying what should not be in the image. For example, if you got an image of a person with hat, you could regenerate it with the same seed and the word “hat” in the negative prompt to get a similar image without the hat.1

There were also innovations in prompting itself, beyond the mere tweaking of words. SD has always weighted words at the start of a prompt more than words at the end, but the AUTOMATIC1111 webui implemented an additional way to control emphasis – words surrounded by () would be weighted more and words with [] weighted less. Add additional brackets to amplify the effect, or control it more precisely with syntax like this: (emphasised:1.5), (de-emphasised:0.5).

People also experimented with changes to the prompt as generation went on. In one method, alternating words, the prompt [grey|orange] cat would use grey cat for step 1, orange cat for step 2, grey cat for step 3, and so on, providing a way to blend concepts in a manner difficult to achieve with text alone. In another method, prompt editing, the first option would be used for N steps, and the second option would be used for the rest. This concept was taken further by Prompt Fusion, which allows more than one change and finer control of the interpolation between prompts.

Prompt Fusion example

Prompt Fusion example

Regional prompting is another useful technique, helping to separate distinct areas of a given prompt – without this, Stable Diffusion will apply colours and details willy-nilly – ordinarily, “a woman with brown eyes wearing a blue dress” may generate a woman with blue eyes and a brown dress, or even a woman with a brown dress and green eyes standing in front of a blue background!

In the days of the 1.4 model, prompting often felt like the be-all and end-all of image generation. Prompting was the only interface exposed by the closed-source online image generators, DALL-E 2 and Midjourney, that immediately preceded SD 1.4’s public release. The model was a Library of Babel which theoretically contained every variation of every image that had ever been made or ever could be made, all of them locatable by entering just the right series of characters. By this may you contemplate the variation of the 600 million parameters.

But even with positive and negative prompts, word emphasis and de-emphasis, prompt editing and all kinds of theorising and experimentation with different words in different combinations, mere language was found wanting as the sole instrument for navigating latent space. It’s possible to get just what you want from prompting alone, in the same way that it’s possible to find a billionaire’s BTC private key in this index.

Models

Since my last post, Stability AI has released several models that improve on that initial model.ckpt (v1.4), most recently Stable Diffusion XL 1.0, a model trained on 1024x1024 images and thus capable of greater resolutions and detail right out of the gate. Where not otherwise noted, I’ve used this model to generate the images in this post.

But rather than wait on Stable Diffusion’s official model releases, people started doing their own training. This took a few different forms. One immediate demand was for the ability to add subjects to the training data – it’s trivial to generate recognisable pictures of celebrities with the base model, but what if you want to generate pictures of yourself or your friends (or your cat)? Enter Textual Inversion, followed by DreamBooth, followed by hypernetworks and LoRA. Some of these approaches create new model files, while others create small files called embeddings that can be used with different base models.

But there’s a lot more to be done with additional training than just adding new faces. CivitAI, the main repository for models and embeddings, contains a plethora of models trained to achieve specific styles and additional fidelity for particular subjects. Most of these are based on Stable Diffusion 1.5, and the most popular ones produce much better results in their specific areas (digital art, realism and anime, mostly). There are even embeddings for facial expressions and getting multiple angles of the same character in one image. Some technically inclined visual artists have even trained models to produce works in their own style.

Some models available on CivitAI

Some models available on CivitAI

Training can be quite computationally intensive, and also requires curation of your own set of captioned images with the right dimensions, so it’s not for everyone. In some sense, training your own models and embeddings is just a more powerful way of using Stable Diffusion’s image input functionality, img2img. Which brings us to…

Image prompting

As we covered in my original SD post, img2img takes an input image in addition to a prompt, and uses the input image as the initial noise for generation. There are many different uses for this:

  • Generating variations of existing images, either with altered prompts or masking, or just different randomness.
  • Generating detailed pictures from crude drawings.
  • Changing an image to a different style.
  • Transferring a pose from one character to another.

Unfortunately, vanilla img2img doesn’t know which of these you’re trying to achieve with a given input, and the only extra variable you get to control is the number of diffusion steps to perform on top of the input image (often termed denoising). One solution to this problem is careful prompt engineering. Another solution is changing the way img2img works.

Last year, various methods were proposed for tweaking img2img to get more consistent results: Cross Attention Control, EDICT and img2img alternative are the ones I know of. The idea with all of these methods was to reverse the diffusion process and find the noise that would produce a given image, and then alter that noise to generate precise, minimal changes.

img2img alternative example

img2img alternative example

When this works, it works really well, but I haven’t had a lot of success with it in my own experiments.

The really useful and impressive advance in this space is ControlNet. ControlNet provides an additional input to the generation process in the form of a simplified version of a given image – this can take a variety of forms, but the most common are edge maps, depth maps and OpenPose skeletons. This produces outputs that are much closer to the inputs used and allow us to specify which aspect of an input image we care about. It can even be used without text prompts.

To use ControlNet, we first create a simplified map of a given input image using a preprocessor (edge-mapper, depth-mapper, pose-extractor, etc) and then pass that as an input to the generation process by applying it to the developing image at each step, either for the whole process or a portion of it, depending on how strong you want the ControlNet’s influence to be.

As we can see from the images below,2 each ControlNet model captures a different aspect of the input image. The canny edge model is concerned with preserving lines, the depth model with preserving shapes, and the pose model concerned only with the human figure and its configuration of limbs.3

ControlNets can be combined, and additional models allow you to capture details such as colours, brightness and tiling patterns. The latter two have been combined to great effect to generate creative images that double as QR codes, as well as compositions like Spiral Town.

IP-Adapter is yet another image-input technique, in which input images are converted into tokens instead of being used as initial noise. This is very a good way of producing images based on a specific face without losing its likeness (and far quicker than training a DreamBooth model or LORA).4

It’s also great for combining images.

All of the images above are one-shot generations for the sake of illustration, but any synthographer worth their burnt-out graphics card knows that to execute on a vision, you’re going to need multiple iterations. You might get a first pass from pure prompting, then use ControlNet to make some variations that preserve a pose from the original, then fix up some individual details with inpainting (or manual image editing), then expand it with outpainting, then combine it with something else through IP-Adapter, and on and on it goes. You might generate a scene’s background separately from its foreground elements, stick them together in an image editor, and then do a low denoising img2img pass to blend it all together.

None of this requires as much effort as, y’know, actually learning to draw or take good photographs, but it takes some ingenuity and experience (and more than a little luck with RNG) to coax attractive results from the machine.

Or so it seemed.

DALL-E 3

Despite being first on the scene, OpenAI’s DALL-E 2 soon lost ground to newer versions of Midjourney and Stable Diffusion. As OpenAI focused their efforts on ChatGPT, their image generator was left to languish. But that all changed at the start of this month, with the public release of DALL-E 3, first replacing DALL-E 2 as the engine behind Bing Image Creator, and then as a ChatGPT plugin for users on a paid plan.

Just like its predecessor, DALL-E 3 takes in a natural language text prompt and generates four different images from it. It’s a lot better, both in terms of image quality and especially in terms of prompt understanding. Let’s compare outputs from the prompt I used at the start of my previous post:

the city of cape town as a martian colony artist's conception landscape painting

And what’s more, if you use DALL-E 3 with ChatGPT, it will write multiple detailed variations of your prompt and use those to generate the four images.5 Here are a couple of results from that, with ChatGPT’s prompts as their captions.

Artistic conception of Cape Town on Mars. The familiar landmarks of Cape Town are juxtaposed against the alien backdrop of the Red Planet. Bio-domes shelter the city, and advanced infrastructure hints at a thriving Martian colony. The vastness of the Martian landscape stretches out, with dunes and rock formations.

Artistic conception of Cape Town on Mars. The familiar landmarks of Cape Town are juxtaposed against the alien backdrop of the Red Planet. Bio-domes shelter the city, and advanced infrastructure hints at a thriving Martian colony. The vastness of the Martian landscape stretches out, with dunes and rock formations.

Futuristic landscape painting of Cape Town on Mars. The city&rsquo;s skyline rises from the Martian surface, with its iconic landmarks protected by large transparent shields. Martian rovers and settlers can be seen, and the vast red desert of Mars extends to the horizon.

Futuristic landscape painting of Cape Town on Mars. The city’s skyline rises from the Martian surface, with its iconic landmarks protected by large transparent shields. Martian rovers and settlers can be seen, and the vast red desert of Mars extends to the horizon.

Some have suggested that this spells an end for prompt engineering, but I’m not so certain. ChatGPT’s expansions create attractive, detailed images, but it may extrapolate things that weren’t intended by the prompter. For playing around and making images to post on social media or in blogposts like this one, that’s fine, but it doesn’t make executing on a specific vision much easier.

Stable Diffusion XL has some limited ability to generate images containing coherent text, a word or two at most. More often than not, this means that random words from the prompt will be written somewhere on the image, necessitating the negative prompt “text”. DALL-E 3, on the other hand, will happily generate whole sentences with one or two errors at most, and only when explicitly asked to write something.

Stable Diffusion XL

Stable Diffusion XL

DALL-E 3 in ChatGPT

DALL-E 3 in ChatGPT

DALL-E 3’s ability to create pleasant, mostly coherent images with plenty of fine detail from text prompts alone (note the application of the word “frantic” in the image above) makes many of the Stable Diffusion techniques above feel like kludgey workarounds compensating for an inferior text-to-image core. Not that there isn’t a place for different types of image inputs, custom training, and funky prompt manipulation, but all of these techniques and tools would be far more powerful and useful alongside a text-to-image model as powerful as DALL-E 3. The potential in pure text prompting is far from exhausted, and OpenAI’s competitors have their work cut out for them.

A clown doing yoga, no ControlNet needed

A clown doing yoga, no ControlNet needed

It’s been suggested that Stable Diffusion is a lot weaker than it might otherwise be due to the low quality of much of the LAION dataset it was trained on – many captions do not correspond to image content and many images are small, badly cropped or bad quality. With the advent of vision models, there’s a case to be made for using AI to recaption the whole dataset and training a new model on the result.6

That would be a good start, but I think it’s also likely that OpenAI is leveraging the same secret sauce present in ChatGPT to understand prompts. As far as they’re concerned, generating pictures is a side-effect of the true goal: creating a machine that understands us.


  1. Most of the time, at least. Diffusion models don’t actually understand English, it just feels like they do. ↩︎

  2. These were generated with SD 1.5 models, as I find most of the current SDXL ControlNet models somewhat lacking. ↩︎

  3. While the pose model captures the general configuration of human limbs, SD is not smart enough to know which limbs are which, or how many of each one a human should have. It gets especially confused with poses in which the person’s hands touch the floor – many discarded generations of the final image showed three-legged clowns. ↩︎

  4. Here again, I’ve used SD 1.5 because of model compatibility. ↩︎

  5. It’s possible that Bing Image Creator also does this under the hood. ↩︎

  6. Update (2023-10-20): according to this paper, this is pretty much exactly how DALL-E 3 was built. ↩︎

Reply via email

]]>
Review: The Excavation of Hob's Barrow https://davidyat.es/2023/08/11/review-hobs-barrow/ Fri, 11 Aug 2023 14:00:51 +0200 https://davidyat.es/2023/08/11/review-hobs-barrow/

The Excavation of Hob’s Barrow is a British folk horror point-and-click adventure game from Cloak and Dagger, published by Wadjet Eye. The latter is a long-time favourite studio of mine, as previous game reviews on this blog will attest.1

The protagonist of Hob’s Barrow is one Thomasina Bateman, an antiquarian primarily interested in digging up barrows (burial mounds) across England. The game is set in Bewley, a remote Midlands village surrounded by desolate moors, sometime during the Victorian Era. Thomasina2 receives a letter from a gentleman named Leonard Shoulder, who invites her to the village to excavate a local barrow. Upon her arrival, Thomasina meets a cast of eccentric and unwelcoming townsfolk, none of whom seem willing or able to tell her much about the supposedly famous landmark or her elusive contact Mr Shoulder. Being a stubborn sort, she presses on anyway, determined to find the barrow and uncover its secrets.

The eastern edge of Bewley.

The eastern edge of Bewley.

Gameplay

The game takes place over a few days, during which Thomasina must explore the village and complete errands for its inhabitants so she can locate and ultimately excavate the titular Hob’s Barrow. The interface is standard for modern adventure games – left-click to interact, right-click to examine, and mouse over the top of the screen to see the inventory. Modern conveniences such as a fast-travel map and to-do list/quest log are welcome inclusions, and there’s also a neat time-saving mechanic that allows you to instantly exit a room by double-clicking.

Thomasina is an upper-crust lady of means, but for gameplay reasons runs out of cash early on, and so must embroil herself in some classic adventure game puzzle solving to get anything she wants – the plot advances through such staples as elaborate NPC distraction puzzles and long chains of fetch quests.

There&rsquo;s also a bit of digging, as one might expect from the title.

There’s also a bit of digging, as one might expect from the title.

Despite my love of the genre, I must confess a distaste for these sorts of puzzles, at least outside of comedic works like Sam & Max or Flight of the Amazon Queen. I’ve previously praised other Wadjet Eye games (Unavowed, Technobabylon, Blackwell Epiphany) for puzzles that felt like integrated parts of the story and game world. On this count Hob’s Barrow is not a complete success. The better puzzles are used to explore the game’s cast and deepen Thomasina’s relationships with them, and contain some touching character moments. Other puzzles feel a lot like padding. One chain, involving a broken fiddle bow, exists only to facilitate a set piece that, while appropriately unsettling, feels poorly integrated into the overall story. Another chain, involving a pail of milk, is yak-shaving exemplified and contains an NPC-distractions puzzle that felt a little too comical for the game’s general tone. But there are enough strong moments and interesting plot twists in this segment of the game for me to overlook even the weaker puzzles.

The final section of the game, which involves actual barrow excavation, consists of a different sort of traditional adventure game puzzle, but one which gels perfectly with the game’s tone and plot, and actually feel like the sort of thing a proto-archaeologist in this kind of story should be doing. Though I wouldn’t go so far as to say the game would have been better with more focus on this section – there’s an undeniable charm to the village of Bewley and its inhabitants that Hob’s Barrow would be much poorer without.

A garden in the sky.

A garden in the sky.

My first playthrough took about ten hours. I’m compulsive about examining everything and exhausting all dialogue options, so that’s probably close to the upper bound of how long you can spend with this game if you don’t get seriously stuck.

Presentation

At first blush, the graphics are standard for a Wadjet Eye game – 2D pixel art that mimics VGA adventure games of the early-to-mid 1990s. It’s beautifully done, with many background animations that make every scene feel alive. More modern-looking particle and lighting effects are used to good effect for rain and fog.

Bewley at night.

Bewley at night.

The game distinguishes itself with detailed close-up animations for dramatic and creepy moments. While none of them are out-and-out jumpscares, they’re very unsettling.

Though a few of them are quite pleasant. Incidentally, this is the least creepy image of this cat in the whole game.

Though a few of them are quite pleasant. Incidentally, this is the least creepy image of this cat in the whole game.

The voice acting is up to Wadjet Eye’s normal excellent standard, and it’s a welcome change of pace to hear British accents in one of these games. “There’s nowt for you ’ere, lass,” will be echoing in my brain for at least a few more weeks.

The music is understated and contributes to the atmosphere. Apart from background music, there are a couple of musical performances in the game, both of which are great, even if one feels a touch modern for Victorian England.

Story

Vague spoilers follow.

As is common for the horror genre, and especially for works inspired by Lovecraft, Hob’s Barrow does not have a happy ending. Choices made during gameplay lead to variations in character dialogue, but there’s only a single ending – nothing you can do will save Thomasina from the fate she hints at in the game’s sombre narration, framed as a letter to her mother written after the game’s events. It reminds me of another adventure game about a highly characterised adventurer archaeologist excavating a tomb.

Infocom’s Infidel, released in 1983, was likely the first adventure game to feature a strongly defined player character who meets an unpleasant end after the player “wins”. In that game, you take on the role of an assistant to a famous and successful adventurer-archaeologist in the cast of Indiana Jones. After receiving a call from a wealthy benefactor wanting to sponsor an expedition to an Egyptian pyramid while your boss is out, you decide to take on the job alone, without informing your boss about it. This doesn’t go well, and the game proper begins like this:

You wake slowly, sit up in your bunk, look around the tent, and try to ignore the pounding in your head, the cottony taste in your mouth, and the ache in your stomach. The droning of a plane’s engine breaks the stillness and you realize that things outside are quiet – too quiet. You know that this can mean only one thing: your workmen have deserted you. They complained over the last few weeks, grumbling about the small pay and lack of food, and your inability to locate the pyramid. Ander after what you stupidly did yesterday, trying to make them work on a holy day, their leaving is understandable.

And after some hours of deciphering pseudo-hieroglyphics, solving mechanical puzzles, and classic adventure game treasure hunting, it ends like this:

You lift the cover with great care, and in an instant you see all your dreams come true. The interior of the sarcophagus is lined with gold, inset with jewels, glistening in your torchlight. The riches and their dazzling beauty overwhelm you. You take a deep breath, amazed that all of this is yours. You tremble with excitement, then realize the ground beneath your feet is trembling, too.

As a knife cuts through butter, this realization cuts through your mind, makes your hands shake and cold sweat appear on your forehead. The Burial Chamber is collapsing, the walls closing in. You will never get out of this pyramid alive. You earned this treasure. But it cost you your life.

This was highly controversial and arguably led to Infidel’s commercial underperformance. While adventure game players would have been no strangers to a grisly death, for it to be presented as the game’s winning ending was a highly unwelcome surprise. It raised new questions about storytelling in games. Could “winning the game” be reconciled with experiencing an unhappy ending? And how should one reconcile the tragic arc of a highly characterised protagonist with the player’s agency?

Nearly forty years later, the ending of Hob’s Barrow has been similarly controversial, though a much better game and story. Infidel tells the story of an irredeemable bastard getting his comeuppance, whereas the protagonist of Hob’s Barrow is an immensely likeable character whose undoing results from her love for her father.

While both stories have their endings firmly locked in from the start – Infidel because it starts after your character has already doomed himself to a lonely death in the desert, and Hob’s Barrow through its framing device – there’s a sense that you might have more control over Thomasina’s fate because you can direct her actions from the moment she arrives in Bewley. The developers could have included an option somewhere to make her wait for the next train out of town and leave the burrow unexcavated, though this wouldn’t make for much of a story.

Many games with well-defined protagonists have been made in the last forty years, and the compromise between this and player choice seems to be giving the player choices between different in-character options. I think most modern players can accept this, and that’s very much the case in Hob’s Barrow. When you encounter a drunkard near the start of the game, you choose whether Thomasina slaps or fobs him off verbally, but there’s no option for her to kill him.

Charming.

Charming.

We can apply the same logic to the overall narrative – Thomasina is driven by compulsions at the core of her personality to excavate the barrow despite the warnings and suspicious behaviour of the townsfolk – to run away would be out of character. I think this is what the writers were going for, and while I’m not opposed to it in principle, a couple of missteps make it less effective than it might have been.

Just before the game’s final segment, a friendly character indicates that he has something important to tell Thomasina that he’s been struggling to remember. The contents of his message are shown to us in a flashback sequence, which reveals a very subtly hinted-at twist. We then return to the present, where another character appears on the scene and shoos away the messenger. It is unclear whether we’re supposed to understand that he told Thomasina the contents of the flashback, or that he was shooed away before he could tell it to her. Neither interpretation is particularly satisfying.

If Thomasina is supposed to be ignorant of the flashback’s events, then it doesn’t fit into the framing device of the letter she’s writing. Besides that, there’s something extremely frustrating about having to direct a character’s actions while knowing that they’re missing an important bit of information you have no way of communicating. I don’t think dramatic irony works in an interactive story.

On the other hand, if Thomasina is supposed to have heard the message, then it’s bizarre that she has no reaction to it and never mentions it again. There’s a difference between having your fate bound up in a tragic personal flaw and just being stupid – imagine a version of Romeo and Juliet where Romeo receives the letter from Friar Laurence but still kills himself.

In the game’s commentary, writer and programmer Shaun Aitcheson mentions that the flashback was added pretty late in the game’s development, after it became clear that testers needed something more to understand the story. I think a better approach would have been to pepper the game with additional foreshadowing and only reveal the twist at the end. Sure, there probably would have been accusations that it came out of nowhere, but this game’s ending was always going to be controversial. Any twist ending, no matter how carefully written, will come off as incongruous to some and obvious to others.

For a tragic story about a flawed protagonist to work in a game, it’s essential that the player comes as close to that character as possible and really understands their perspective, to the extent of being blinded by it. Hob’s Barrow undermines itself by stepping away from that to telegraph its ending.

Conclusion

Despite my quibbles with some of the game’s puzzles and storytelling choices, I had a great time with it overall and would heartily recommend it to fans of indie adventure games and folk horror. That said, players who prioritise their own agency and ability to choose between multiple endings may want to steer clear.


  1. When I was looking into Cloak and Dagger’s previous work, I discovered that they were behind A Date in the Park, a free-to-play adventure game that I rather disliked and left a snippy negative Steam review for. This game is much better! ↩︎

  2. An old-fashioned female name often shortened to and now subsumed by variants of Tamsin. ↩︎

Reply via email

]]>
Review: The Lost Room https://davidyat.es/2023/08/06/review-the-lost-room/ Sun, 06 Aug 2023 18:53:50 +0200 https://davidyat.es/2023/08/06/review-the-lost-room/

The Lost Room is a television miniseries that ran on the Sci-Fi Channel in late 2006 to low ratings and mixed reviews. Intended as a launchpad for a longer series that was never made, it instead became a minor cult classic and (perhaps) the inspiration for a whole subgenre of internet horror fiction. Recently, I watched it for the second time.

The protagonist is a policeman, Joe Miller, who comes into possession of a motel room key with special properties. Turn the key in any compatible lock on any door, and that door will open into an empty motel room. If you enter the room and close the door, it won’t open into the same place you left behind. If you visualise another door anywhere in the world while holding the key, the door will open there. Not a bad way to get around.

The room in question

The room in question

Early on in the series, it’s revealed that the key is one of many seemingly mundane objects with supernatural abilities, ranging from powerful to totally useless. There’s a bus ticket that teleports people to a bus stop in New Mexico, a pen that microwaves anything it touches, and a wristwatch that… hard boils eggs. There are about one hundred of these objects, and they all came from the motel room. They cannot be destroyed. When combined, they produce even stranger effects.

As Joe discovers more about the objects and the titular room, he comes into contact (and often conflict) with a myriad of different groups and individual collectors, all pursuing their own agendas: some are mere collectors, while others believe the objects to have divine significance. Before long, Joe crosses the wrong group and his own quest to learn about the objects turns from an idle curiosity to a matter of extreme personal importance.

Joe&rsquo;s primary facial expression throughout the series

Joe’s primary facial expression throughout the series

Though it’s only three 90-minute episodes long, The Lost Room packs more backstory and plot developments into that runtime than lesser shows manage in entire seasons. The effects and interactions of the objects and rules that govern them are well thought-out and consistent, making most of the twists gratifying rather than arbitrary. The premise remains fresh even nearly twenty years later – this is a story about supernatural events that doesn’t lean on religion, witchcraft, aliens, folklore, or any other common tropes, choosing instead to make its own distinctly modern mythology. The worst criticism I can make is that the romantic subplot comes off as wooden and entirely perfunctory.

The miniseries ends with a few loose ends and an obvious sequel hook, belying the creators’ intention to expand it, but still tells an essentially complete story and won’t leave you feeling ripped off.1

It’s often said that the Velvet Underground’s first album only sold a few thousand copies, but everyone who bought a copy started a band. The Lost Room appears to have had a similar effect on the world of internet horror fiction. Either that, or there was a case of creative synchronicity going around at the time. The series aired in December 2006, and in early 2007, both SCP-173 and The Holder of the End appeared on 4chan. Both of these pieces spawned long-running collaborative internet horror fiction projects built around numbered lists of objects with strange properties – namely, SCP and The Holders. The latter, with its exhortation that the objects “must never come together” and a collector named Legion is especially synchronistic.

In any city, in any country, go to any mental institution or halfway house in you can get yourself to.

In any city, in any country, go to any mental institution or halfway house in you can get yourself to.

You can get the gist of both projects by reading their first entries. SCP-173 is written like an internal memo or database record describing a numbered object. It starts with Special Containment Procedures, a description of how the object is to be contained – this builds up some tension and curiosity about what the object is and does, and why these measures are necessary. The containment procedures are followed by a terse description of the object, which is horrifying and bizarre, but detailed in clinical and meticulously precise language.

A format like this lends itself to imitation, and thus SCP was born. Each of the hundreds of entries describes a bizarre object or creature that has been captured and secured by the SCP Foundation, in the style of the original SCP-173. Longer entries add logs of experiments and interviews, and liberally employ redaction to enhance the “top secret” feeling of the documents.

The Holders series is written in a looser style, evoking urban legends. The first Holders entry, The Holder of the End, is shlocky and slightly overwritten, but kinda evocative all the same. Written in second person, it provides a list of instructions for retrieving an object from a mysterious individual who can be found in “any mental institution or halfway house you can get yourself into”,2 mostly consisting of things to say and things to avoid doing. The prize for following these instructions correctly is one of 538 objects which every entry reminds us “must never come together”.

With a line like “That object is 1 of 538”, The Holder of the End was begging for imitators. Most of the additional ~537 follow the same format, beginning with “In any city, in any country” and ending with “They must never come together.” As the entries go on, the elaborate rituals become more and more elaborate, as do the gruesome consequences for failure at any stage. But at a certain point, this becomes more comical than frightening.

Knock on the second door from the left twelve times in a perfect dodecahedron pattern and then cough at precisely 86dB. If you’re even one decibel off, a demon will appear and drag you to hell by your nostrils, where you will be tortured for all eternity. Every day your ears will be pulled off and your tongue cut out, only to grow back in a more hideous and mangled form the next day.

I made up the above quote to avoid picking on any particular entry, but a lot of the stories are like this. The longer Holders stories will have many of these kinds of paragraphs, showing plenty of enthusiasm but none of the restraint required for good horror writing. The lower quality SCPs often fail in similar ways, eschewing the terseness and fragmented feeling that is essential for the series’s atmosphere in service of the writer’s enthusiasm for sharing their cool monster.

What makes the original pieces in both series work for me is all that is left unsaid. The best microfiction implies infinitely more than it explicitly describes. SCP-173 hints at the existence of other strange objects, and of a vast bureaucracy that keeps them hidden from the world. The Holder of the End hints at mystical hidden places in everyday locations, and at people irrationally driven to brave terrible danger in service of collecting objects that should not be collected.

To go back to The Lost Room, much of the allure and mystery of the objects lies in their randomness and inexplicability. Both qualities inevitably diminish as the work continues. So perhaps it’s for the best that it ends where it does.


  1. The same can unfortunately not be said of the creator’s more recent television pilot, Parallels, which is deceptively packaged as a movie on some streaming services. I enjoyed it, but I guess the network wasn’t looking for an updated Sliders↩︎

  2. These sorts of lists are a common creepypasta form outside of The Holders, being mostly elaborate variations on classic folklore rituals such as “go into a candle-lit bathroom at midnight and say Bloody Mary three times in the mirror”. ↩︎

Reply via email

]]>
The Last Battle https://davidyat.es/2023/07/26/narnia/ Wed, 26 Jul 2023 11:41:03 +0200 https://davidyat.es/2023/07/26/narnia/

Netflix is developing a new adaptation of CS Lewis’s classic children’s fantasy series, The Chronicles of Narnia. The series has been partially adapted before, into a BBC TV series in the 1980s and a film trilogy by Disney and Walden Media in the 2000s, which covered four and three of the seven books respectively. Little is known about the project, except that they have the rights to all seven books this time, so most articles about it are opinion and speculation. I read one such piece recently which really stuck in my craw.

Netfllix&rsquo;s Chronicles of Narnia Reboot Using The Original Books&rsquo; Ending Would Be A Huge Mistake

Netfllix’s Chronicles of Narnia Reboot Using The Original Books’ Ending Would Be A Huge Mistake

This article argues that the final book, The Last Battle, should be changed for the adaptation, essentially for commercial reasons. The core of the author’s argument is made beneath the Orwellian heading “The Chronicles Of Narnia’s Ending Doesn’t Work In 2023”:

The actual ending of The Chronicles of Narnia novels won’t work as the ending to the upcoming Netflix adaptation. In the realms of literature and fantasy, few works have captivated readers as enchantingly as The Chronicles of Narnia. The timeless tales of Narnia have always been steeped in allegory, mainly influenced by Lewis’s Protestant Christian beliefs. The story’s heavy religious undertones, particularly at the end of the final volume, The Last Battle, risk alienating those from other faiths. Even those who share that faith may not wish to see such a heavy-handed portrayal in an escapist fantasy series.

Apart from reading like it was generated by ChatGPT, this text makes me wonder if the writer is even familiar with Narnia. In the first1 and most well-known book, The Lion, the Witch and the Wardrobe, we are introduced to Aslan, the Great Lion, who is Narnia’s true king and its divine creator. At about the midpoint of the narrative, Aslan offers himself as a sacrifice in place of the traitor Edmund Pevensie. He is tied to a stone table and put to death by the evil White Witch. The next day, he comes back to life and ends the White Witch’s century-long tyranny over Narnia. Remind you of anything?

Christian themes are pervasive in western literature – at a certain level of abstraction everyone is Jesus in Purgatory. But you don’t have to go hunting for them in Narnia. Who Aslan was supposed to be was obvious to me at the age of six. Lewis was completely unsubtle about this aspect of the work, to the extent that even calling the series a Christian allegory would be too indirect.

If Aslan represented the immaterial Deity in the same way in which Giant Despair [a character in The Pilgrim’s Progress] represents despair, he would be an allegorical figure. In reality, however, he is an invention giving an imaginary answer to the question, ‘What might Christ become like if there really were a world like Narnia, and He chose to be incarnate and die and rise again in that world as He actually has done in ours?’ This is not allegory at all.

CS Lewis, Letter to Mrs Hook, December 1958

The idea that an adaptation should avoid or tone down the stories’ Christian themes is completely incoherent, whatever your disposition towards those themes, as they’re the very purpose of the work. Picking on just The Last Battle here seems oddly selective; really, the phrase “heavy-handed portrayal” describes Narnia far better than “escapist fantasy series”. Without the Christian elements, you simply don’t have Narnia.

A key difference (and source of disagreement) between Lewis and his friend Tolkien was that the latter strove to create a self-contained world and story that could be appreciated without reference to anything in the real world, and the former did, well, pretty much the opposite of that. So, with Lord of the Rings, any Christian themes are there for you to take or leave as you choose. By contrast, Lewis presents Narnia as a world parallel to our own, which human characters visit and have experiences in that are directly relevant to their lives in our world. He had very specific goals in writing Narnia and made them quite plain to the reader, all but spelling it out:

In your world, I have another name. You must learn to know me by it. That was the very reason why you were brought to Narnia, that by knowing me here for a little, you may know me better there.

Aslan, Voyage of the Dawn Treader

<a href="https://reparrishcomics.com/post/618585931338874880/facebook-twitter-instagram-redbubble-buy">Tolkien vs Lewis: Allegories by RE Parrish</a>

Tolkien vs Lewis: Allegories by RE Parrish

Narnia may not be the right property to adapt if you’re uncomfortable with overt Christian themes.

In the next paragraph of the article, we get a few more criticisms of The Last Battle:

Beyond the issue of religion, the ending of The Chronicles of Narnia is bleak and unsatisfying. The sudden demise of all but one of the main cast is disheartening. Moreover, the ending focuses on characters introduced only in the final book, creating a sense of disjointedness from the overarching narrative. If Netflix follows the outline of this original ending, it risks sparking controversy, like Game of Thrones. To avoid falling into the same pitfalls, The Chronicles of Narnia must navigate a delicate balance between preserving its timeless charm and crafting an ending that appeals to the diverse sensibilities of contemporary viewers.

Though this paragraph starts with the words “Beyond the issue of religion”, its contents belie that. Every issue cited in this paragraph comes back to religion. To judge the ending as “bleak and unsatisfying” is a highly materialist view that misses the point entirely, echoing the in-book sentiments of Griffle and his group of dwarves.

And to call it disjointed is to miss the actual overarching narrative, which is that of the world of Narnia, from its creation in the chronologically first book, The Magician’s Nephew, to its destruction in The Last Battle. Just as The Lion, the Witch and the Wardrobe parallels the death and resurrection of Jesus, these books parallel the creation story in Genesis and the apocalypse in Revelation. Narnia is not supposed to be about the lives of the four Pevensie siblings, and to compare Lewis’s careful artistic choices to the final season of Game of Thrones, which wasn’t even based on a finished book, is facile.

The rest of the article is in much the same vein, permeated by the claim that a faithful adaptation of the Narnia books would be rejected by audiences, who have “diverse sensibilities”, by which the author clearly means “intolerance for Christian children’s books written by a mid-century English professor”. But if that’s true, then why would Netflix even adapt the books in the first place? Surely Lewis’s views can’t be that intolerable to modern audiences if they already like his work and want to see a new adaptation of it.

There’s an underlying push to have a real story with a real message shorn of its identity and turned into a theme park. A desire for Narnia to become a safe, commercialised “universe” populated by bland, interchangeable content that conforms to every current moral fashion and keeps people paying their streaming subscriptions. To cynically use a classic and enduring children’s series as a mere brand. I would greatly prefer a thousand articles about how Narnia represents everything that is most hateful about religion to this mealy-mouthed appeal to commercial blandness.

The Last Battle opens with Shift the Ape bullying Puzzle the Donkey into wearing the skin of a dead lion and impersonating Aslan. Let us hope that Netflix does not intend to do this with the Narnia books. Better, then, that they can the project and adapt Philip Pullman instead. Or maybe they could create something original? Unless, of course, that also Doesn’t Work in 2023.


  1. By publication order, not chronological order. ↩︎

Reply via email

]]>
Postmortem: Ludum Dare 53 https://davidyat.es/2023/05/20/postmortem-ludum-dare-53/ Sat, 20 May 2023 18:11:22 +0200 https://davidyat.es/2023/05/20/postmortem-ludum-dare-53/

This is a postmortem for Causality Couriers, my entry into Ludum Dare 53. I’ve kept spoilers to a minimum, but you might want to play it before reading anyway (it’s pretty short).

It’s been eight years and twenty contests since the last time I successfully finished a game for Ludum Dare. I’ve attempted one or two in the interim, as well as some other jams, but that was the last time I was reasonably happy with the output. There are actually quite a few things that have to come together for me to actually complete one of these successfully.

  1. The theme has to be inspiring. This is perhaps more about my state of mind than whatever the theme is; sometimes, I just can’t come up with an idea I like for a given theme. Not that I don’t have strong opinions about what constitutes a good theme – see below.
  2. The jam needs to fall on a weekend when I have a relatively empty schedule.
  3. The idea I come up with needs to be doable in a weekend.
  4. The idea needs to be something I won’t completely change halfway through.

Ludum Dare 53 (theme: “Delivery”) kicked off on the weekend of 29 April, perfectly positioned between two public holidays. That satisfied criteria 1 and 2. Criteria 3 and 4 were not satisfied, but fortunately Ludum Dare has recently introduced a new category, Extra, which allows participants to submit their game any time during the three week judging period which follows the main weekend. Games submitted in Extra are not in the running for winner of the Competition (48 hours), or even the Jam (72 hours), but can still get feedback and ratings, which is the main attraction in any case.

This ended up being a pretty long post, so here’s a table of contents:

Concept

Much like my previous LD entry, I Hunger, Causality Couriers is text-based. This is one of the easier sorts of games to make in a short timeframe. The controls and gameplay, such as it is, come pre-defined: click on links or type into a parser. The content is easy to generate, for a writer – rooms, objects, characters, can all be constructed in a few lines of text. Challenges arise and bugs appear, but if, like me, your main gamedev skills happen to be writing and programming, you’ll be completely in your element dealing with such things. Also you can get away with leaving out things like graphics and sound, both of which would be indispensable for any other type of game.

I came across Robin Johnson’s Gruescript around the time it was initially release in late 2021 and had quite a bit of fun playing around with it – I even wrote a Vim syntax file for the language. The project I first started in it never quite came together, but the tool stuck in my mind as something I wanted to use. It strikes a very satisfactory balance between a hypertext tool like Twine and a heavyweight parser interactive fiction tool like Inform 7 – gameplay is mouse-driven1 and there’s a simple world model with rooms, objects and people. The language itself is terse and very fit for purpose.

Apart from using Gruescript, I wanted to experiment with AI, mostly by using Stable Diffusion to illustrate the game, but also by having ChatGPT as a creative assistant.2 It wasn’t any good at writing Gruescript, having essentially none in its corpus, but it was very handy for brainstorming. On the Saturday morning after the jam began, I asked it for some ideas.

None of these ideas were bad, but they all seemed a bit obvious, so I asked for some more:

I went with number 2 (though number 3 was a close second). A time-travelling courier bring packages from the future to the past seemed like a cool concept with a lot of potential.

Story

In the initial version of the game, I had the player start off in a delivery hub with three unmarked parcels, all different sizes and shapes. The delivery hub also contained three different time portals, and the idea was that the parcels had all lost their tags and you had to guess which one should go where. It was going to be possible to deliver any of the three packages to any of the time periods, and depending on which one was delivered where, the present time would change and so would later time periods.

This, of course, was a classic combinatorial explosion. I started mapping out all the different possibilities and realised that not only would it be very time-consuming to do every different path justice, but it would ultimately make this game very similar to my last Ludum Dare game, I Hunger, in which a human society develops on different lines depending on what combinations of sacrifices are demanded by a volcano god. The other problem was that the different time periods, packages and changes to history were kind of arbitrary and the story didn’t really feel coherent.

At this point I was halfway through the Jam period and found myself needing to scrap a lot of content and return to the drawing board. Luckily there was still the three-week Extra category, so I decided to go through with a radical rewrite and not pressure myself to finish it by the Monday evening. I pared the story down to a single package, and opted to start with the player collecting the package from a more interesting location than a delivery hub. The player would then proceed to deliver the package to one of the locations I’d already implemented, allowing me to salvage a fair amount of content from the original version.

I managed to get about 70% of the game implemented by the Monday evening, i.e. most of the content in the two main areas. But there was still the matter of the illustrations I wanted to add. Up until that point, I’d been developing in Gruescript’s online interface, as it allows for rapid testing and has some neat debugging features. However, it does not provide any means for editing the game’s HTML, which I would have to do if I wanted to include illustrations. So on Monday evening, I shifted to local development.

Engine hacking

Luckily, I’ve played around with local Gruescript development before,3 and the engine is simple and cleanly written enough to be very amenable for hacking on. When you export a game from the online interface, you get an HTML file containing three things:

  1. The HTML and CSS which makes up the interface. This is mostly hardcoded, though Gruescript does let you change the colours of most things from your game code.
  2. The JavaScript that powers the game engine. This is pretty simple and easy to follow – many elements of Gruescript code map directly to JavaScript functions.
  3. Your own Gruescript code, included in a textarea at the bottom of the page. This code is evaluated when the page is loaded to create the game’s content.

The CSS wasn’t quite as responsive as I’d have liked, so I rewrote some of it to use flexbox. Given more time I probably would have changed more of it. I also had to add an area for illustrations, and create a JavaScript function for changing the illustration. I made a few alterations to the game engine to do things like make the controls disappear when the game ends. At the last minute before I was about to publish the game, I remembered that Gruescript included save and load functionality, and had to make some quick hacks to ensure that functionality was illustration-aware. There were still a couple of bugs with these engine and interface changes that I fixed4 after another Jam entrant brought them to my attention.

I didn’t want to have to edit my Gru code inside a massive HTML file, so I copied it to its own .gru file, and had ChatGPT write me a build script in Python, which just copied the latest contents of the .gru file into the HTML file’s textarea. With these few things in place, I had a solid local development workflow, and could continue development with the game in its intended interface.

Game code

Being a highly specialised DSL, Gruescript looks very different from a standard C-like language. It reminded me most of the language used by AGT,5 mixed with a bit of Inform 7, but far more terse than either. Its syntax is very simple, containing little nesting or even special characters. Rooms, objects and characters are defined in room and thing blocks:

# A ROOM
room before_barricade You're standing in front of an enormous barricade made of junk.
prop display Before the junk barricade
prop year Unknown year
tags start

# AN OBJECT
thing sword Courier's Blade
desc This is the first job you've had where a massive sword is considered required equipment. 
carried
tags portable

# A PERSON
thing fletcher shifty character
name shifty character
desc The man looks to be in his sixties, with a white beard and a mane of grey hair.
tags alive male conversation
loc wooden_shed
prop callme Fletcher
prop start_conversation "You must be the courier," says the man, looking you up and down. "Name's Fletcher." Your collection tag mentions a Darius Fletcher – this must be him.
prop end_conversation "G'bye then."

Each of these blocks starts with an identifier and a description, followed by series of properties – the room’s exits, the object’s initial location, the text to use when starting a conversation with a person, and so on. Each room and thing can also have a set of tags: some of these are meaningful to the engine – portable, for example, means the thing can be picked up and placed in the player’s inventory – but you can also create your own tags for your own purposes. You can also define custom attributes with prop, which can be strings or numbers.

Moving from object definitions to functions, we have the verb block. Here we can define what a given verb used with a given thing will do.

verb twist collection_tag
say Twisting a collection tag will instantly transport the holder to the place and time of the parcel's collection. But only once, and you've already used this one.

Verbs can also be defined generically, with the special variable $this used to refer to the thing involved.

verb twist
say You twist the $this.

A noun has a set of attribute definitions, and a verb has a sequence of commands to execute and expressions to evaluate. Initially, it was unclear to me how complex conditional logic was intended to work. A simple, single path Gruescript verb definition might look like this (comments are prefaced with #):

verb eat lunchbox # defining a verb called eat on a thing called lunchbox
carried $this # is the player carrying the lunchbox?
has $this full # does the lunchbox have the tag 'full'?
untag $this full # remove the 'full' tag from the lunchbox
say You devour the banana, peanut butter and marmite sandwiches in your lunchbox. # print some text

The carried and has lines are expressions. If an expression evaluates to true, execution continues to the next line; if it evaluates to false, execution of the entire verb block ends. This means that if the player is carrying a full lunchbox, the full code will execute and the final say line will be printed. If the player is not carrying the lunchbox, or has already eaten from it, nothing will happen.

Gruescript provides simple syntax for printing something in the event of an expression’s failure – just append : to the expression line and add the message after it.

verb eat lunchbox
carried $this: You'll need to pick it up first.
has $this full: The lunchbox is empty.
untag $this full
say You devour the banana, peanut butter and marmite sandwiches in your lunchbox.

This syntax is a bit confusing at first, but works well for a lot of adventure game puzzles, where you often have to take a number of intermediate steps to achieve some goal. However, I soon found cases where I needed to do more than just print a message when an expression was false. In some instances, the right solution was to flip the logic (e.g. !has $this full will be true if $this does not have the tag full), but in other instances I needed a sequence of multiple commands for both true and false cases.

After rereading the documentation and perusing some of Gruescript’s example code (especially the ~2000 LoC implementation of The Party Line), I realised that the solution was to create multiple verb blocks with opposite expressions.

verb eat lunchbox
carried $this
has $this full
untag $this full
say You devour the banana, peanut butter and marmite  sandwiches in your lunchbox.

verb eat lunchbox
!carried $this
give $this
say You pick up the lunchbox and look inside.
has $this full: The lunchbox is empty.
untag $this full
say There are some banana, peanut butter and marmite sandwiches here. You devour them.

Per the documentation, when a verb button is clicked, Gruescript will evaluate every applicable verb block from specific to general, stopping when one succeeds (runs all the way to completion without a failed expression). The same is true for other procedural logic blocks in the language.

Once I understood that properly, I was able to use Gruescript pretty proficiently. The engine provides a js command for executing arbitrary JavaScript, which I used to change the current illustration and force items into holding section of the player’s inventory at certain moments – for example, to make the player hold the collection tag at the start of the game.

Overall I’d say I had a positive experience with the language, but I would have liked some syntax for embedding conditionals inside strings, like what Inform 7 does with square brackets:

Candy Storage is a room. "As you enter the room, the white cubes inside all turn to face you.[if unvisited] They're square candies that look round.[endif]"

This code will only print the second line of the room description the first time you enter the room. And you can add an else statement and nest the conditions for truly varied text. To do something similar in Gruescript, which only supports direct string interpolation, you need a bit more code and some creative hacks.

room candy_storage As you enter the room, the white cubes inside all turn to face you.{candy_storage.extra}
prop extra They're square candies that look round.

verb go
at candy_storage
tag candy_storage visited
write candy_storage.extra &amp;zerowidthspace; # setting a variable with nothing will give it a value of 0. To avoid printing 0 in our room description, we need to set it to an invisible character instead.
continue

Gruescript was designed for more textually minimal games than Causality Couriers ended up being, games in the spirit of the highly terse Adventure Interational text adventures by Scott Adams, so it’s understandable that this wasn’t an implementation priority.

Ludum Dare encourages entrants to release their source code (this is a hard requirement for the 48 hour event), so I’ve made all of the code available in this Github repository.

Illustrations

I’ve been playing around with Stable Diffusion since its initial public release, and one of the purposes I’ve long planned for it is creating the art for a game. Illustrating a text adventure seemed like an appropriately humble first outing – as long as I could get pictures that roughly corresponded to my room descriptions, I could use them. There would be no great need to worry about animation or even (with a small enough game) creating consistent characters across different images.

Since my post about Stable Diffusion in August 2022, the tech has advanced in leaps and bounds. In the beginning, it was all about carefully crafting prompts to wring as much as possible out of the Stable Diffusion 1.4 model, but since then there’s been an explosion of custom models for different subjects and art styles, as well as sophisticated tools like ControlNet.

I usually generate a picture of a cat right after opening the <a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">SD UI</a> to test that everything&rsquo;s working.

I usually generate a picture of a cat right after opening the SD UI to test that everything’s working.

So, armed with a custom model and a few months of prompt engineering experience, I went to work generating illustrations. I wanted one for each location, one for each character, and a few more for specific events. For most of the pictures, I played around with the prompt and random seeds until I got something that looked good, and then fed it into img2img at varying denoising strengths to finetune.

A couple of the pictures started out as crude GIMP sketches, but I have a habit of making these as terrible as possible just so that I can feel astonished when the AI denoises into something good. As a result, the images I liked best from this process were invariably the ones that bore the least resemblance to my initial sketch. Being a bit more intentional with the initial sketch would definitely give me greater control over the final result. Here are some of the final pieces:

Illustrations for a text adventure are pretty low-stakes – the player does not need to physically move around in them and the game is entirely playable without them. Because of the lack of real practical constraints, and the limited amount of time I could (or wanted to) spend on any one image, I tended to be happy once I got something that broadly corresponded to the room or character description, even if it didn’t have all the details I would have wanted. The illustration didn’t need to be something the player scrutinised for gameplay-relevant details, it just had to create an impression. In a couple of cases, I modified the corresponding description to better fit an image I liked, but this didn’t happen too often.

The one case where I wasn’t able to get something I wanted was the interior of a tent in the second part of the game. This was probably the most specifically described room in the game, and I had a clear mental picture of it as being very sparsely furnished. I couldn’t get the AI to play along with this – it kept adding all sorts of junk to the scene, no matter how much I loaded the negative prompt. So in the end I just threw up my hands and generated a nice image of a tent exterior to use (pictured above). I could probably have gotten closer to my original vision with a decent sketch and some time dedicated to inpainting, but I didn’t feel that such a minor room (which the player doesn’t even have to visit) was worth the effort.

I did some minor clean-up and alterations on a couple of images – scrubbing a TV screen and manually adding bars to a prison cell. There were a couple of places where I superimposed a character image on an existing background, and I also used a GIMP filter or two when it was easier to do that than to try get what I wanted out of prompting.

Overall, I’m satisfied with the illustrations I was able to put together in half a day of work, and feel that they add a lot to the game’s mood. In future, I’d like to spend more time with the process and use more and better input sketches. I’d also like to try a more demanding project, such as animated character sprites, or a navigable environment. I know it can be done!

Below, some cool pictures I didn’t end up using:

A lot of other games in this Ludum Dare made use of AI art to varying degrees, from a few posters in Phantom Package to most (all?) of the non-UI art in The Dirty Inbox. There was even a game for which all of the art, text and music was AI generated.

Release

A nice thing about doing Ludum Dare in 2023 is that most modern gamedev platforms can export to HTML5: Unity, GameMaker, Godot, RPGMaker, Adventure Game Studio, even PyGame all have some way to produce games for browsers. I still had to get the Windows VM out for a few games, but a surprising number of even quite graphically intensive games worked perfectly in my browser. Thank you WebAssembly!

My own entry was already a webpage, so no conversion was needed. Reception was quite positive, although, as mentioned above, there did turn out to be a bug with saving and restoring games, related to some of the interface changes I’d made to Gruescript’s default HTML. But that was easy enough to fix once it was reported to me. Testing is always going to be less than perfectly thorough on a one-man highly time-limited project.

A few commenters remarked on the lack of background music, and I recall getting this feedback last time as well. I did think about adding music and spent a bit of time scrolling through Incompetech, but ultimately it felt like any music I could add would be an afterthought and not really integrated. AI-generated music might have been fun to play with, but I didn’t think of that until literally right now.

After a couple of text games for Ludum, I think I’d like to do something more graphical next time. But then, you never know where the theme is going to take you…


  1. There are ways to show hyperlinks in an Inform game, but I’m not sure if you can disable the parser entirely. ↩︎

  2. This immediately disqualified me for the 48 hour event, as Ludum Dare has yet to formulate rules about LLM-assisted development and asks that developers keep anything of that nature to the Jam and Extra categories. Use of more primitive AI generation tools is permitted though. I’m hopeful that this will change in future events. ↩︎

  3. I’ve even made a Vim plugin for highlighting Gruescript syntax↩︎

  4. In proper game jam spirit, I’ve restricted post-release updates to bug and spelling fixes. ↩︎

  5. Incidentally, this was the first language I ever tried programming in, sometime in primary school. I wrote many lines of AGT code but was unable to compile them because I didn’t understand file extensions. ↩︎

Reply via email

]]>
Walkthrough: Causality Couriers https://davidyat.es/2023/05/16/walkthrough-causality-couriers/ Tue, 16 May 2023 06:05:17 +0200 https://davidyat.es/2023/05/16/walkthrough-causality-couriers/

Now that Causality Couriers has been out for a week or so, it’s time to release the official walkthrough. The game has six endings, and in the spirit of 100% completion, we will visit each one, though not in order.

Before you read this walkthrough and spoil the story, ask yourself the following question:

Have I tried tampering with the parcel when there’s no one around? How hard have I tried?

If you’re satisfied with your answer to that question, read on.

Knock on the barricade and wave at the circular shadow when it appears. It’s a drone!

To get ending 1 of 6: Save here and dodge the drone repeatedly, until you get too tired and the drone kills you. Then restore your save and continue.

Strike the drone with your sword to dispatch it. Examine its remains, or do anything else for one turn, and an opening will appear in the barricade.

Go through the barricade and into the wooden shed. There you’ll meet Darius Fletcher, who has a parcel for you. Examine the walls of the shed and talk to Fletcher about various subjects. He will give you the parcel.

Remove the delivery tag from the parcel and examine it. How strange, it’s for someone in Ancient Egypt!

Twist the delivery tag to travel across time and space to the parcel’s intended recipient: an alien observing the construction of the Great Pyramid of Giza (observing, not helping). Before you do anything here, save your game. Your next few actions will determine which of the five remaining endings you can reach.

Talk to the alien and deliver the parcel.

The alien will open the parcel, drink the wine inside and instantly die. Oh no! But what’s worse, a horde of angry aliens will suddenly descend on the scene, appearing from portals. Unfortunately for you, they’ve never heard the phrase “don’t shoot the messenger”. Save here.

To get ending 3 of 6: Attack the alien hordes with your sword. It’s at least ten against one, so you won’t have a chance. At least you will feast in Valhalla.

To get ending 4 of 6: For a few turns, do anything except attack the alien hordes or escape to the desert. Look at the scenery and fiddle with your inventory until the aliens surround you.

Right after the aliens appear, go into the desert. If you move far enough away from a delivery or collection point, you will automatically be teleported back to your home time and place. After escaping the aliens, you’ll find yourself back in your apartment.

Turn on the television, and then try to turn it off again. A special broadcast appears, and you’re getting the headache you usually experience after one of your deliveries changes the past. The special broadcast appears to be about you.

From here, watch TV and look at the poster on your wall when it appears. You can proactively hand yourself in the police outside, or you can sit and watch TV until they bust your door in. Either way, you’re going to prison.

In your cell, look at the poster and play a sad tune on your harmonica. A new collection tag will appear in the middle of the room. Pick it up and give it a twist to get ending 5 of 6. This is one of the two endings where you don’t die. Whether it’s a good one or not is up to you.

Restore the game from the save you made just after arriving in Egypt. Then, take a closer look at the parcel. It can be opened, but wouldn’t be appropriate to do this in front of the alien, so duck inside the tent and do it there. You’ll need to click on open a few times, as your courier’s instincts make it very difficult to tamper with the mail.

Eventually, you’ll open the parcel and find a wine bottle inside. You can open this bottle as well, but it will also go against your courier’s instincts, so keep trying.

To get ending 2 of 6: Save here and drink the wine from the open bottle.

Empty the wine bottle. Then hold the parcel again and close it, which will make you pack away the wine.

Leave the tent and deliver the tampered parcel to the alien. It’s pretty weird to send empty bottles to people, but seems to be a harmless prank. With your delivery done, you can wander off into the desert and be teleported home.

In your apartment, you’ll see a closed envelope on the coffee table. Pick the envelope up and open it. When you take the letter out and unfold it, a humanoid being of pure light will appear in front of you. Talk to the being – your dialogue choices will not affect the final outcome.

You will be informed that Causality Couriers is aware of your parcel tampering, but apart from fining you some portion of the fee you were to receive, they will impose no punishment. As long as you don’t interfere with the business operations of Causality Couriers or prevent the company from being founded, they don’t care if or how you change the past.

Once you’ve finished speaking the being, they will disappear, leaving a new collection tag behind. Pick up and twist this tag for ending 6 of 6.

To get the Hustler Achievement, ask both Fletcher and Gthxrynn for payment. The game records how many times you shake the parcel and will tell you at the end – initially this was going to have some effect, but it got cut.

If you’re feeling proactive, you can also open the parcel in the first area of the game, by returning to the front of the barricade before you twist the delivery tag.

Reply via email

]]>
Game: Causality Couriers https://davidyat.es/2023/05/09/game-causality-couriers/ Tue, 09 May 2023 18:11:02 +0200 https://davidyat.es/2023/05/09/game-causality-couriers/

After an eight year break, I’ve written another text adventure game for Ludum Dare 53. I didn’t quite make the deadline for either the 48-hour Comp or the 72-hour Jam, but these days they have an Extra category with a very generous three week deadline, and I managed to squeeze into that.

Causality Couriers can be played over here. Its Ludum Dare page is here. It was made with Gruescript, a point-and-click text adventure creator.

CAUSALITY COURIERS

The development of this piece was an exercise in (1) using Gruescript and (2) using AI. Most noticeably, illustrations for the story have been generated with Stable Diffusion. All the prose is mine, but I used ChatGPT as a creative consultant throughout the writing process – it was the AI that came up with the notion of a time-travelling courier in the first place.

I have tested it a fair bit, but probably not exhaustively, so you may encounter some bugs. I’ll put up a more detailed post-mortem after Ludum Dare 53 officially ends on 20 May.

Reply via email

]]>
Nine years go by https://davidyat.es/2023/04/07/nine-years/ Fri, 07 Apr 2023 16:57:19 +0200 https://davidyat.es/2023/04/07/nine-years/

It’s been a dry year since my last blogiversary post. I’ve beaten the previous record of only four posts by publishing only two.

I’ve been working on a follow-up post about Stable Diffusion and that other AI thing everyone’s talking about at the moment, but every time I finish up a draft, some new development comes along that I have to include – LoRA, ControlNet, etcetera and so forth.1 At this rate, by the time I have that post ready, ChatGPT may be able to write something better.

Speaking of ChatGPT, in a previous retrospective post, I discussed the fetishisation of content creation as its own end:

A few years ago I read a short ebook that excitedly proclaimed it would teach the reader to write a book in a weekend. From what I recall of the content, the idea was to do a bunch of research on Saturday and then go for a walk on Sunday and narrate your book into a recorder app on your phone for later transcription (perhaps by a gig worker in SE Asia). Bam! You’ve written a book! You’re a writer!

A blog post I read recently proposed getting into the writing habit by assembling articles through (1) copy-pasting a whole bunch of paragraphs from your research about some topic, (2) ordering them and (3) systematically paraphrasing each one. Bam! You’ve written a series of regular blog posts! You’re an influencer!

I question the value of a book written in a weekend or a series of articles assembled through paraphrasing. In an overreaction to a perceived passivity in culture, the act of creation is fetishised even when the product is worthless. Writing down things you learn is often a useful way to properly understand and internalise them, but there’s more to creation than watering down things extracted from elsewhere into a shallow soup. You have to actually bring something of your own to the dish. And that’s hard work that most people can’t do on command and with regularity.

The pointlessness of this kind of empty content is even more apparent in a world where everyone with an internet connection has access to a powerful LLM.2 The writing method proposed above can be entirely replaced with a bit of prompting, followed by light edits. Depending on the output, you may have to manually fix up some hallucination, but ChatGPT can probably help you with that as well. Forget writing a book in a weekend, you can now write a book in a couple of hours. But many things that can be generated by LLMs may not be worth generating.

Source: <a href="https://marketoonist.com/2023/03/ai-written-ai-read.html">Marketoonist</a>

Source: Marketoonist

ChatGPT has a lot of very real and useful applications such as writing boilerplate code and boilerplate copy. But boilerplate content exists to support something. For this reason, I don’t think LLMs will replace writers any more than diffusion models will replace artists. The most rote and dull forms of both kinds of work will be automated, but someone’s still going to have to guide the process. The most interesting and powerful results in all forms of AI content creation will continue to come from clever human beings guiding and shaping the AI’s output, using methods and workflows ultimately not that different from those of traditional writers and artists.

Despite what you may hear about the coming AI apocalypse, none of these things have their own will or vision. ChatGPT is ultimately just a fancy Markov chain, which requires human input as a starting point.

And that’s why I will continue writing this blog, at a rate of at least one post per year.


  1. There is also only so much time you can spend looking at demoniacally misshapen horrors, a large part of the AI image generation workflow. ↩︎

  2. Large language model, the class of thing that ChatGPT is. ↩︎

Reply via email

]]>
Goodbye Blackberry https://davidyat.es/2022/12/15/goodbye-blackberry/ Thu, 15 Dec 2022 23:20:22 +0200 https://davidyat.es/2022/12/15/goodbye-blackberry/

I recently took advantage of a Black Friday sale1 to buy myself a new smartphone. This had been a while coming, as my previous phone was on permanent battery saver, had stopped receiving Android updates or even security patches, and was never quite the same since I had to replace its screen earlier this year. Any sensible person in my position would have bought a new phone months ago, but you see, dear reader, my previous phone was a BlackBerry – “the last BlackBerry in the world,” I often called it.

The BlackBerry Key2

The BlackBerry Key2

The Key2, released in 2018, is the latest BlackBerry model on the market. Barring a miracle, it will forever retain that title. Unlike classic BlackBerry phones, it runs Android – BlackBerry phones haven’t run BlackBerry’s custom QNX-based operating system2 since the unsuccessful BlackBerry 10 series of phones, some of which didn’t even have keyboards.

Before the Key2, I had a BlackBerry Priv, the first BlackBerry to run Android. I bought it pretty much as soon as I found out about it, having spent a desperate year exiled to Samsung after the untimely death of my last BlackBerry Curve.

The Priv remains the best phone I’ve ever had. I managed to squeeze five years of life out of it, by which time the battery was totally shot and the OS had long ceased receiving updates. The phone’s design was brilliant – at first glance, it looked like any other glass slab smartphone:

The BlackBerry Priv, seemingly a normal Android smartphone&hellip;

The BlackBerry Priv, seemingly a normal Android smartphone…

But with a subtle press and flick of the thumb, the screen would slide up to reveal a hardware keyboard!

Tada!

Tada!

And throughout five years of daily use, heavily favouring the hardware keyboard over the onscreen one, the sliding mechanism remained smooth and fault-free.

The operating system was stock Android, with the addition of a very small collection of apps ported from the days of BlackBerry’s own OS – most notably, BlackBerry Hub, which combines notifications and messages from email, phone, messaging and social media apps into a single inbox view. I never got into the habit of actually using BlackBerry Hub in the same way I’d used it on older BlackBerries, but it and the other apps were unobtrusive and mostly didn’t attempt to duplicate Google functionality or make me sign up for online accounts, which is more than can be said for certain other Android OEMs.

The Key2’s brick design was less inspired than the Priv’s, but it worked well and looked good. The fingerprint scanner on the spacebar was a welcome addition – I only got a phone capable of fingerprint authentication after iPhone users had already moved on to FaceID – and the smaller screen size never caused any problems with apps or general phone usage (but then, I don’t watch much video on my phone). The OS was a bit newer than the Priv’s, and it came with a few more neat apps, such as Redactor.

Both phones managed to recapture the BlackBerry keyboard magic and combine it with the Android operating system in a way that felt totally natural and normal. Between these two phones, I haven’t had to use an onscreen keyboard since 2015.

But all good things must come to an end. One problem with buying phones with abnormal designs and form factors (and that no-one else owns) is that it’s very difficult to find covers or screen protectors for them. This was especially egregious for the BlackBerry Priv, which had not only a slide-out keyboard, but also a curved screen. Still, I managed to keep it in pristine condition for five years.

My Key2 was not so lucky. Around the middle of this year, I dropped it on some bricks and cracked the screen. A small crack at the top of the screen might have been survivable, but this crack was at the bottom of the screen, and rendered the back, home and app switcher buttons unusable.

So I ordered a replacement screen off Amazon. After paying for priority shipping and then waiting a week for the screen to get through customs anyway, I finally got it. I checked out some YouTube videos and for about three seconds, I considered doing the replacement myself. Then I came to my senses and took it to a repair shop. My cracked screen was replaced, and my phone appeared to be restored.

I’m not sure whether it was faulty workmanship by the shop, or a faulty part from Amazon, but over the next few weeks, I had to press harder and harder to get the back, home and app switcher buttons to register. Then, for a while, the app switcher button would press itself, and my phone would go through bouts of insanity, jerking between the switcher and my current app and even occasionally going split-screen. This was mercifully short-lived, as the buttons soon stopped working altogether. So now I was back in the same position as when my screen was cracked, albeit with less chance of getting bits of glass in my thumb.

Fortunately, Android on BlackBerry has a shortcuts menu in Settings that allows you to configure keyboard shortcuts for apps you use a lot (press W for WhatsApp, that kind of thing). You get a short and long press shortcut for all 26 letters of the alphabet, giving you a total of 52 app shortcuts. Unfortunately, these shortcuts don’t work if you’re already in an app, only if you’re on the home screen. Without the three buttons at the bottom of the screen, once I entered an app, I was unable to return to the home screen without rebooting my phone. However, it was possible to launch the action associated with any shortcut from the shortcuts menu itself.

My new home screen.

My new home screen.

My workflow for switching apps became:

  1. Open the pull-down notifications screen.
  2. Tap the gear icon near the top.
  3. Tap Shortcuts & gestures, then Keyboard shortcuts.
  4. Find the right letter and launch its shortcut app.

This is pretty straightforward for things like “W for WhatsApp”, but gets a bit less so once you have more than two apps that start with a given letter. Luckily I don’t use too many different apps on a daily basis.

Another side-effect of having no app-switcher is not being able to close apps. When an Android device starts running out of RAM, the memory manager app pops up a notification to tell you. You can then force-kill some apps you’re not using until RAM usage returns to the green zone.

So my BlackBerry was technically usable, but between the quickly expiring battery, the lack of software updates, and this app-switching problem, I decided it was time to upgrade. I’m travelling internationally this December, for the first time since February 2020, and I didn’t want to traipse around Europe with a half-functioning phone.

Various promises have been made about a new BlackBerry model in 2021 or 2022, but all have come to naught. The BlackBerry company itself does not make phone OSes or phone hardware anymore. Physical keyboards are an extremely niche feature in the smartphone world of undifferentiated glass slabs.

The best option for a physical keyboard these days seems to be a phone from Unihertz. They have two main keyboard phone models, both funded partially by KickStarter campaigns. There’s the Unihertz Titan:

BlackBerry Curve: Battle Edition

BlackBerry Curve: Battle Edition

As much as I like phones with keyboards, I still want something that vaguely resembles a modern smartphone. So there’s also the Unihertz Titan Slim, which was released this year:

Slim is relative term.

Slim is relative term.

Hmm. Looks familiar.

I heavily considered getting one of these to replace my Key2. At first glance, it’s the same phone, but with a modern operating system and (presumably) a functioning screen. However, a few points made me reconsider.

  • Aesthetically, I don’t like how they’ve moved the fingerprint sensor above the keyboard. Compared to the Key2, the design looks poorly thought-out and kind of redundant, almost.
  • The reviews of the phone are not great. Specs don’t seem to be anything spectacular, and the phone has a slippery plastic finish that feels cheap, very unlike the matte BlackBerries, and even the keyboard is kind of pokey – at the very least, it’s strictly inferior to a BlackBerry keyboard.
  • The Titan Slim doesn’t have a headphone jack. I feel very strongly that Apple’s decision to remove the headphone jack from their phones was a stupid one. I avoid Apple products to get away from design decisions like that, but unfortunately the industry seems determined to copy every one of their dumb ideas due to what I can only assume is an inferiority complex. I mean, come on, you’re making a phone for people who like physical keyboards! The Venn diagram for keyboard users and headphone jack appreciators is one circle.
  • Finally, any Unihertz device I bought would have to be imported, which would mean paying a whole lot in shipping and taxes and waiting a few weeks that I didn’t have. And all I’d have to show for that would be a worse version of my BlackBerry.

So I decided to return to the dark side. On Black Friday, I bought a Xiaomi. Just your bog-standard glass slab Android phone without a keyboard (but with a headphone jack!). Like most of Xiaomi’s range, it’s got surprisingly good specs for a surprisingly low price. It’s been great having a modern Android OS, masses of RAM and storage space, a functional screen, and a battery that can last me two and half days. All the unremovable crapware that duplicates stock Android functionality is less great, but it’s not as bad as a Samsung, so I can hold my nose and live with it.

But having to typing on a phone screen again truly feels like a step backwards. When I send messages now, it feels as if I’ve lost some essential motor function in my fingers. Compared to rapidity and precision that my keyboard gave me when typing out messages, tapping on a phone screen is maddeningly slow, and autocomplete is wrong often enough to really irritate me. I’m starting to see why people send voice notes.

I typed a lot on the first few BlackBerries. I would compose blogposts, stories, notes, all kinds of stuff, with just my thumbs. There was a time when my thumb typing was faster than my finger typing. But I haven’t actually used a phone like that in over a decade, so on some level I have to acknowledge that a physical keyboard is not actually an essential phone feature for me. Ultimately, I can live with the imprecision and clumsiness of an on-screen keyboard, at least until someone makes an actually good keyboard phone with all of the essential ports.

Maybe we’ll get Neuralink first.


  1. I think it was around 2017 that the world’s marketing executives got together to decide that Black Friday should be an international event, despite there being no tradition of it outside the USA. In South Africa, it has been marked largely by humdrum specials and retailer disappointment. They pretend to have specials, and we pretend to buy them. ↩︎

  2. The last releases of BlackBerry OS reached end-of-life at the beginning of this year. But you can still develop for it if you really want to↩︎

Reply via email

]]>
Stable Diffusion https://davidyat.es/2022/08/31/stable-diffusion/ Wed, 31 Aug 2022 09:58:57 +0200 https://davidyat.es/2022/08/31/stable-diffusion/

On the 22nd of this month, Stability.ai released Stable Diffusion, a free, open-source AI tool for generating images from text prompts (and other images). Previously, access to this technology was available through Open AI’s commercial, invite-only DALL-E 2 platform, Midjourney’s commercial Discord bot, and, in a significantly reduced free-to-use form, Craiyon/DALL-E Mini. Basically, you had to use a hosted service and pay per image, or use something too simple to be much use for anything more than amusing jokes to post on social media.

Stable Diffusion’s public release is a massive sea change. Now, anyone with a reasonably powerful computer can use this tech locally to generate strange and fantastical images until their graphics cards burn out. While DALLE-2 is still probably the more powerful system, able to generate better results from shorter prompts, outputs from Stable Diffusion can rival those of its commercial competitors with a bit of careful tweaking – tweaking made possible by users’ increased control over its parameters.

For comparison, two images made with the same prompt:

the city of cape town as a martian colony artist's conception landscape painting

DALL-E’s is more lush and naturalistic, whereas Stable Diffusion produced something very precise and, well, clearly computer generated, though not without its charm. Moreover, the other three images DALL-E generated for this prompt are just as good, whereas I had to fiddle with different samplers and iteration counts to get something I liked as well from Stable Diffusion. But, crucially, making extra iterations didn’t cost me any credits.

Stable Diffusion really shines when you give it a style to imitate. This can be an artist’s name, an animation studio, or something like “comic book inks”. The best results often combine multiple artist names and styles. I quite liked what I got from having Flemish Renaissance painter Joachim Patinir redo my Martian Cape Town.

city of cape town, mars, martian colony, artist's conception, highly detailed, 8k, intricate, artstation, landscape, by Joachim Patinir
Stable Diffusion (Euler Ancestral sampler)

Stable Diffusion (Euler Ancestral sampler)

In addition to specifying a prompt, Stable Diffusion allows you to tweak the following parameters:

Seed
The random seed used to initialise the generation. By controlling the seed, you can create reproducible generations, which is key to experimenting with prompt and configuration variations – using slightly different prompts with the same seed gives you some idea of what part of the image each word is responsible for. This is exactly the kind of technical detail that DALL-E’s slick, user-friendly interface doesn’t let you touch.
Sampler
The k-diffusion sampler used to generate each iteration of the image from the last one. /u/muerilla provides a useful illustration of this on /r/StableDiffusion:
I use k_euler_a most often because it produces the quickest results.

I use k_euler_a most often because it produces the quickest results.

Steps
The number of sampling steps to take, also illustrated by the figure above. Initially, additional steps will lead to a more coherent and detailed image, but this reaches a point of diminishing returns somewhere in the hundreds of steps.
CFG scale
Also called the unconditional guidance scale, this is a number specifying how closely you would like your output to align to your prompt. The default value is 7.5. Lower numbers allow Stable Diffusion more artistic licence with the output, generally resulting in trippier images. Higher numbers ensure you’ll get what you asked for, but the results sometimes be feel crude or overcooked, reducing Stable Diffusion from a skilled artist to a Photoshop beginner. Generally, higher CFG scales work better with more detailed prompts and higher step counts. The images below have the same seed and prompt, but different CFG scales:
Dimensions
Image dimensions will significantly affect your output. Stable Diffusion was trained on 512*512 resolution images, so this is the recommended setting. Anything bigger than 1024*1024 is not officially supported and can cause tiling, and anything smaller than 384*384 will be too messy and artifacted to come out looking like anything. Also, high output resolutions require powerful graphics cards. My system, on the lowest end of the supported hardware, can’t handle anything bigger than 448*448.

Below, I’ve generated three images with the same seed, prompt and other configuration values, but different dimensions:

Stable Diffusion can generate more than just landscape paintings. It’s also pretty good at portraits, with enough of the right keywords.

Above, I’ve used the same seed with slightly different prompts to generate portraits in the same style of two people who could be siblings. DALL-E 2 and Midjourney both let you generate variations of images you like, but what’s actually varied is a bit random. The ability to tweak a prompt for the same seed lets you have much more control over the direction a variant goes in. We can even get (roughly) the same character at different ages:

Things get a little dicier once you move from portraits to full bodies. Stable Diffusion is prone to generate extra arms and legs at lower CFG scales, and even otherwise perfect pictures usually come with deformed hands and feet.

Trying to get anything in motion – characters walking, eating, etc – often leads to horrific nightmare images not unlike the ones generated by Craiyon, but in ghastly high resolution. This is where Stable Diffusion’s other tool, the image to image generator, comes in. It’s a variation on the text to image generator that lets you specify an original input image to work with, in addition to a prompt. This can literally be a crude MS Paint drawing. I made a steampunk robot:

Digital artists are using this with sketches to great effect.

But you don’t just have to start with your own drawing. You can start with a photo, and convert it into a painting, or vice versa. You can also take output from text to image, draw some coloured blobs on the parts you want to change, and get image to image to redraw it.

Note how the final image even fixes the Batman’s double right foot on its own. We would probably need to go through a few more iterations to get the faces looking decent. For example, we could generate the faces on their own, shrink and paste them in, and then use image to image produce a blended result. For more targeted fixes, some Stable Diffusion GUIs have built-in masking interfaces, where you can select one single part of an image to regenerate, avoiding the small changes you’re bound to get on multiple runs over the whole thing. And we can also use other AI tools like GFPGAN and ESRGAN to fix faces and increase resolution.

Writing prompts for the AI is a bit like learning a new language. A lot of the words you can use in prompts are straightforward and logical – “portrait of a man” will come out as you expect. But as you get into more complex territory, you have to learn the peculiar meaning of terms like “trending on artstation” and try to find ways to ask for different angles without getting a lot of cameras and fish eyes in your results. You can start to figure it out by doing a lot of trial and error with the same seed, adding and removing terms; by looking at what others have generated; by searching the training dataset; or even by generating prompts from images. And if none of that works, you can buy prompts other people have figured out.

Amusingly, terms like “masterpiece” and “award winning” also help to produce attractive outputs. “Just draw an award-winning masterpiece for me, would you?”

It’s amazing how far this technology has come from dog face mosaics. And it’s still developing at a break-neck pace. I mentioned using seeds and prompt variations to generate variations of the same character above, but there’s a much more robust way to do this called textual inversion, which you can test out now if you have a very beefy graphics card. Hopefully, DALL-E-style outpainting will also come to Stable Diffusion.

The question of copyright is a fraught one. Fundamentally, these AIs work by taking in a huge volume of images, most of them copyrighted, and mushing them together in a black box that no-one really understands. You could argue that this is similar to how a human artist might learn things, but these AIs far outstrip the ability of any human across many metrics. Fundamentally, they’re inhuman. And when you can get them to generate images drawn in the styles of living artists, well, not all of them are going to be receptive to that. Simon Willison brings up the point that AIs could be trained on public domain works only, but whether you as an individual are going to use the ethically sourced AI or the standard one is going to be a personal choice.

I’ve personally been much more interested in using Stable Diffusion than I ever was in text generation systems like GPT-3 and AI Dungeon. I’m a far better writer than artist, so the idea of generating text from a prompt is an amusing diversion at best. If I want some text, I’ll just write it myself. Although some artists are already integrating AI into their workflows, others will probably have the same meh reaction to its applications in their field.

Whatever happens now, this toothpaste is not going back in the tube. The cloud continues to correlate its contents.

Reply via email

]]>
Eight bloggy years https://davidyat.es/2022/04/07/eight-years/ Thu, 07 Apr 2022 16:57:19 +0200 https://davidyat.es/2022/04/07/eight-years/

Between this retrospective and the last one, I’ve published exactly four posts on this blog. Suffice it to say, my attention over the past year has been devoted to other projects. I don’t think that’s a bad thing – my attitude towards blogging has long been to eschew any kind of self-imposed schedule or arbitrary rule, and beyond that, to avoid posting anything prematurely. If a post has to languish in drafts for years before I find the right way to phrase its final sentence, so be it!

The more of these retrospectives I do, the trickier it is to come up with something new to say. So let’s take another look at some old things I’ve had to say.

Top five posts (2014–2022)

According to my analytics, these are the five most popular posts of the last eight years (in descending order):

GPU passthrough (2016)

At one time, this was probably the most comprehensive and user-friendly guide to GPU passthrough online. I imagine it’s out of date now, but I’m still using the same setup as I was when I wrote it, and it’s kept working through a few OS upgrades. I’m not sure what would happen if you tried to set this up on more modern hardware, because I haven’t upgraded anything in my desktop PC since I wrote this. When I eventually do upgrade, I’ll probably publish a new version, especially if I end up switching to an Nvidia card.

RSS: nothing better (2017)

This one got to the top of Hacker News once, but still has an order of magnitude fewer views than the GPU passthrough article. I think it still holds up. I wrote it in a single sitting after being prompted to sign up for one too many email newsletters. Sadly, RSS has not supplanted email newsletters in the meantime, but the situation hasn’t worsened by too much. I’m able to subscribe to Substack newsletters with Fraidycat, so we can’t give up hope just yet.

Encrypting a second hard drive on Ubuntu (post-install) (2015)

I published this one because there was nothing else online that told you how to do it, and it’s been very useful for setting up new laptops over the years. The technology hasn’t changed, so I don’t think it’s in need of an update.

Creating a personal wiki with Vimwiki and Gollum (2017)

I had to submit a PR to Gollum to get this working the way I wanted it to. Since writing this tutorial, I’ve also used Roam and Obsidian for maintaining personal wikis. Roam is the most fully featured, but it’s browser-based and online, which can be a bit of pain (and $15/month is quite pricey). Obsidian has some neat features, but it’s an Electron app, which feels wasteful. Ultimately, this Vimwiki/Gollum solution is still quite competitive. But the most important thing with a personal wiki, I think, is to pick one solution and use it for everything rather than have all your notes scattered across half a dozen apps. Don’t make my mistake, kids.

Writing a LaTeX macro that takes a variable number of arguments (2016)

This was something I discovered while writing a fairly complex LuaLaTeX template, and it ended up being a pattern I reused a lot. I like the post a lot as a tutorial, and others have told me they do as well. It’s light, breezy, and finds space for a little bit of humour while still getting to the point quickly. Most importantly, it explains exactly what all of the code you have to write is doing and why it’s part of the solution. There’s nothing that bothers me more than a tutorial with magical code you just have to include without understanding why.

So it’s very sad that there’s a shortcoming in the macro implementation. Technically speaking, it consumes the entire line rather than just the arguments enclosed in {}s. Someone emailed me about this once with a solution, but it involved enclosing all the arguments in an outer {} which I wasn’t a fan of because it looks like you’re just passing in one argument. Ultimately I decided I was happy to live with the macro’s shortcoming for this reason and to avoid complicating the tutorial.


The lesson from this trip through the archives is clear. To maximise engagement, I must write a post about implementing RSS updates for an encrypted personal wiki written in LaTeX and compiled to GPU-accelerated WebGL pages. Time to get to work.

Reply via email

]]>
Review: Existentially Challenged https://davidyat.es/2022/02/27/review-existentially-challenged/ Sun, 27 Feb 2022 10:32:46 +0200 https://davidyat.es/2022/02/27/review-existentially-challenged/ I first discovered Yahtzee Croshaw’s work through his highly acclaimed freeware point-and-click adventure game, 5 Days a Stranger. Being a teenager with an insatiable hunger for freeware adventure games, I soon played through the rest of his catalogue. Having a parallel insatiable hunger for free written content, I read through the archives of his comedy site and all of his free fiction, including two unpublished novels.1 Yahtzee’s prodigious output of freely available writing seemed squarely aimed at a nerdy teenage boy with lots of spare time and a mobile internet connection over which only text was affordable.2

Since then, I’ve kept up with Yahtzee’s work in the realms of both game development and fiction writing, but for a while I found myself less and less interested in each book he released.

I liked Mogworld, a comedic fantasy that takes place inside an off-brand World of Warcraft, but in some ways I felt like I was reading a third iteration of the two novels mentioned above.

Jam, the post-apocalyptic novel featuring man-eating jam, was okay – the central joke wore thin quite quickly and so did most of the others.

Will Save the Galaxy for Food, a comedic sci-fi work about space adventurers, just felt like a rehash of previous work, and at this point I thought that maybe I was just overexposed to Croshaw’s particular tics and subject matter and should stop buying his books. Particularly grating was his need to elaborate on every synonym as if his books are just extended Zero Punctuation episodes, showing how one can over-play to one’s strengths.

The next book he wrote was Differently Morphous. I bought it on Audible3 and it quickly became my favourite work of his. Rather than yet another sneering wise-guy protagonist in a semi-parodic fantasy/sci-fi setting, the story follows a nervous young Type-A woman in basically modern Britain, with a supernatural twist. Between a grounded setting in a real place the author knows well, a shake-up in the usual character roster, and certain amount of tonal similarity to his horror games (his strongest work IMO), Differently Morphous was a much-needed breath of fresh air in Yahtzee’s written oeuvre.

Existentially Challenged is the sequel to Differently Morphous and delivers much the same experience as the original. Characters are fleshed out a bit more, ongoing plots are furthered, and all this is illustrated with an oversupply of tortured metaphors and similes (although he has toned this down somewhat since Will Save the Galaxy for Food).

Few writers can resist making some reference to or comment on current events in their novels. Much of the humour in Differently Morphous comes from applying 2010s social justice language to supernatural beings – demon-possessed individuals are called “dual-consciousness” and the book’s title, “differently morphous”, is a euphemism used to refer to slime monsters. In Existentially Challenged, Yahtzee turns the clock back to the 2000s and tackles organised religion. A significant chunk of the book deals with the question of how today’s organised religions would react to incontrovertible proof of the existence of magic and supernatural beings.

Whatever strengths Yahtzee may possess, subtle and insightful cultural satire outside the narrow domain of video games is not one of them, but I think this is largely for lack of trying. An entire section of Jam revolves around a one-note joke about hipsters doing things ironically (tired even in 2012), and Existentially Challenged’s treatment of the divine is about as deep. On the side of organised religion, Yahtzee gives us a meek Anglican vicar who loses every argument and quickly replaces him with a caricature of an American evangelical preacher who can say about five different sentences, all of which contain the word “Lord”. And so the whole subject of religion versus magic ends up being little more than a plot device.

Now, I certainly wouldn’t expect Mr Croshaw to present a sympathetic portrayal of religion, but his treatment shows a minimum amount of thought in this subject that plays such a large part in his plot and gives the book its very title. Disappointing, but then, we can’t expect too much from a guy who literally used to wear a fedora.

Missed opportunities aside, I enjoyed the central mystery plot of Existentially Challenged, which kept me guessing all the way to the final reveal, without sacrificing logic or previously established rules. I was also intrigued enough by elaborations on the series arc that I’m eager to pick up third book in the series when it comes out in a few years, as opposed to the third book in the Jacques McKeown series, which I will also pick up, but mostly out of loyalty and appreciation of the many hours of free entertainment Yahtzee’s work has given me over the years.


  1. Around this same time, I read John Scalzi’s Agent to the Stars, Jason Pargin’s John Dies at the End. This was after exhausting myself on stuff from Project Gutenberg and looking for free fiction that was a bit more modern. ↩︎

  2. Croshaw is most famous for the Zero Punctuation video game review series, but mobile browsers didn’t support video in the 00s, and if they had I wouldn’t have wanted to spend the data. ↩︎

  3. Yahtzee’s fame largely rests on the rapid-fire narration of his video reviews, and so the author-narrated audio releases of his books tend to precede their written releases by a number of months. Fortunately he narrates them at a slower clip than his ZP videos. ↩︎

Reply via email

]]>
Review: Impostor Factory https://davidyat.es/2021/10/16/review-impostor-factory/ Sat, 16 Oct 2021 20:16:32 +0200 https://davidyat.es/2021/10/16/review-impostor-factory/

Impostor Factory is the third major entry in Freebird Games’s series about helping people with terminal illnesses die happily by altering their memories. I’ve been very positive about the first two entries in previous reviews, and this one continues the trend, though it has a few key differences.

The previous two games both started with Sigmund Corp employees Eva Rosalene and Neil Watts visiting a elderly client to alter his memories. Here, we play Quincy, a nondescript everyman, attending a party at a slightly rundown manor. We find out that the party is being held by two elderly scientists, for the purpose of presenting a new invention to investors. Quincy, occupation and history unknown, seems uncomfortable and out of place among the wealthy investors and eccentric scientists. He becomes still more uncomfortable when the murders begin.

And so Impostor Factory fakes us out a couple of times. First, it looks like it’s going to be a murder mystery. Minutes later, the previously deceased victims are alive again, and walking around like nothing happened. And then they’re dead again. And then they’re alive. And then it looks like the story is going to be a time loop murder mystery. And then it changes again, and we find out what’s really going on, and remind ourselves that this is, after all, a sequel to To the Moon and Finding Paradise. After the initial fake-outs, the story ends up being broadly similar to those of previous games, if somewhat darker, though still punctuated with light and even absurd moments.

If To the Moon was about achieving catharsis through memory alteration and Finding Paradise was about the needless anxiety Sigmund Corp’s service could create, Impostor Factory is a synthesis of both, with catharsis following anxiety. If you could redo your life over and over, what different choices would you make, and what does that say about the choices you originally made, and about the person you are? If you do enough differently, would you still be you?

The previous games were quite light on actual gameplay, and Impostor Factory streamlines this even further. The mini-games and matching puzzles of previous instalments have been elided – the closest thing we get to a puzzle is a sequence near the start where you have to coax a cat out of hiding to get a key.

There&rsquo;s also a cat chase sequence that isn&rsquo;t a puzzle, so don&rsquo;t try to catch him.

There’s also a cat chase sequence that isn’t a puzzle, so don’t try to catch him.

The overall progression is also much more linear. In the previous games, there were a lot of relatively freefrom sections, where you could click around and investigate things in your own order, but these are far and few between in Impostor Factory. Story-wise, this makes sense – unlike the previous player characters, Quincy is not a trained professional methodically moving through someone’s memories trying to alter them, but a hapless everyman trying to make sense of a confusing situation, who is led by other forces for much of the game.

Interactivity is sacrificed to give the story a stronger forward momentum, and that’s really been the main focus of all these games, so I won’t call it a major loss.

The art exceeds the bar set by Finding Paradise, with a large number of animations bringing a surprising amount of emotion to the tiny pixillated characters. Unless you really hate pixel art, this game is a constant delight to look at. And while the music once again fails to recapture the lightning in a bottle that was To the Moon’s soundtrack, it’s nonetheless fitting and a great accompaniment. A credits song by Laura Shigihara is notably absent for the first time in the series (a deliberate choice by Kan Gao).1

Once again, if you enjoyed the previous games in the series, you’ll probably enjoy this one too. It’s not a good one to start with though – the story gets a bit inside baseball, and there’s a twist towards the end that will only be more confusing if you haven’t play those. So if you haven’t started the series, well, consider this a recommendation of all three games.

TIL this is a real Pizza Hut in Giza.

TIL this is a real Pizza Hut in Giza.


  1. This loss was so acutely felt, that Pealeaf, who hums on the game’s credits track, produced a bonus song for the game↩︎

Reply via email

]]>
Research first https://davidyat.es/2021/06/30/research-first/ Wed, 30 Jun 2021 18:00:40 +0200 https://davidyat.es/2021/06/30/research-first/

I’ve quoted the story of Master Wq and the Markdown acolyte before on this blog:

A Markdown acolyte came to Master Wq to demonstrate his Vim plugin.

“See, master,” he said, “I have nearly finished the Vim macros that translate Markdown into HTML. My functions interweave, my parser is a paragon of efficiency, and the results nearly flawless. I daresay I have mastered Vimscript, and my work will validate Vim as a modern editor for the enlightened developer! Have I done rightly?”

Master Wq read the acolyte’s code for several minutes without saying anything. Then he opened a Markdown document, and typed:

:%!markdown

HTML filled the buffer instantly. The acolyte began to cry.

In addition to showing off one of the little features that makes Vim such a great editor, this little parable illustrates a broader point about modern software development that’s essential if you want to achieve practical things without wasting a lot of time. The point being that you if you have a problem that appears to call for a code solution, you should always do a thorough evaluation of existing solutions before writing one yourself.

I’ve made the mistake of not doing so a few times myself. Most recently, I was working on a project that involved a fair bit of browser automation. Or rather, I looked at the problem, thought about similar problems I’d solved in the past and what I’d done to solve them, and come to the conclusion that browser automation was the correct approach. I decided all of this without making a single web search or questioning any of the assumptions I was making about the problem space.

In the abstract, if you need to write code to interface with a web-based system of some kind, there are four general approaches you can take, depending on the affordances and restrictions of the system and its ecosystem. In ascending order of both implementation difficulty and brittleness of the solution, these are:

  1. Find a library in your chosen programming language that wraps the target system’s API. This is the easiest and best approach, but it depends on the existence of a such library and such an API.
  2. Interact with the target system’s API through raw requests. This depends on the existence of such an API, and will be more or less difficult depending on the API’s documentation.
  3. Scrape the target system’s web pages. You have to do this if there isn’t an API that can give you the data you want in a nice format.
  4. Use browser automation to simulate a real user interacting with the system. You have to do this if there’s too much JavaScript for you to scrape anything meaningful.

In this instance, I jumped directly to number 4 without properly interrogating whether 1 through 3 were possible, just because previous experience with similar but not identical problems told me they weren’t.

After wrestling with time delays and picking through HTML soup for a few days, I had a janky version of what I wanted that basically worked, provided the internet speed stayed roughly constant and you were prepared to wait a few minutes for the thing to run. It was at this point that I started searching around for alternate methods to achieve at least some of the things I’d implemented. I imagined that I might be able to engineer some kind of hybrid solution, perhaps between (4) and (2).

Imagine my shock when I found a relevant tutorial that used method (1). There was a nice library for doing everything I wanted to do, written in the language I was using. I reimplemented everything in a few hours, using less code, and ended up with a solution that was infinitely faster and more reliable, allowing me to proceed with other aspects of the project far quicker than I’d expected to.

Lesson learnt: before writing any code, do some research. Search for what you’re trying to do, in whole and in part. Anticipate some of the searches you might do while writing your code, and make those in advance. Make sure that you eliminate the easy solutions before moving onto the hard ones. Don’t reimplement %!markdown in Vimscript, and don’t do with browser automation what you can accomplish with a library.

Reply via email

]]>