~sqwishy/froghat.ca

0771adc6de7f194f568f2eae19be3877115aeb50 — sqwishy 2 months ago 78a070f




        
38 files changed, 543 insertions(+), 1 deletions(-)

M froghat.ca/2024/05/grounded-mumble/grounded-mumble.rst
A froghat.ca/2024/06/atlast/billboard-reith.png
A froghat.ca/2024/06/atlast/billboard-vanilla.png
A froghat.ca/2024/06/atlast/cat.jpg
A froghat.ca/2024/06/atlast/gamefont-reith.png
A froghat.ca/2024/06/atlast/gamefont-vanilla.png
A froghat.ca/2024/06/atlast/index.rst
A froghat.ca/2024/06/atlast/manifest.png
A froghat.ca/2024/06/atlast/zoomed-noalpha.png
A froghat.ca/2024/06/civ4-ui/better-shadow.jpg
A froghat.ca/2024/06/civ4-ui/bibracte-shadow.jpg
A froghat.ca/2024/06/civ4-ui/bigfont-3840x2160.jpg
A froghat.ca/2024/06/civ4-ui/chaiNNer-lm.webp
A froghat.ca/2024/06/civ4-ui/chaiNNer.webp
A froghat.ca/2024/06/civ4-ui/citybar-cozy.jpg
A froghat.ca/2024/06/civ4-ui/citybar-toosmol.jpg
A froghat.ca/2024/06/civ4-ui/citydetails-3840x2160.jpg
A froghat.ca/2024/06/civ4-ui/civ5bar.jpg
A froghat.ca/2024/06/civ4-ui/contrast.jpg
A froghat.ca/2024/06/civ4-ui/crunchy-shadow.jpg
A froghat.ca/2024/06/civ4-ui/final-3840x2160.jpg
A froghat.ca/2024/06/civ4-ui/index.rst
A froghat.ca/2024/06/civ4-ui/meow.jpg
A froghat.ca/2024/06/civ4-ui/monitor-1994-dm.jpg
A froghat.ca/2024/06/civ4-ui/monitor-1994-lm.jpg
A froghat.ca/2024/06/civ4-ui/nifskope.jpg
A froghat.ca/2024/06/civ4-ui/pin-normal.jpg
A froghat.ca/2024/06/civ4-ui/pin-smol.jpg
A froghat.ca/2024/06/civ4-ui/rectangles-dm.svg
A froghat.ca/2024/06/civ4-ui/rectangles-lm.svg
A froghat.ca/2024/06/civ4-ui/scaledgamefont-crop.jpg
A froghat.ca/2024/06/civ4-ui/scaledpy-3840x2160.jpg
A froghat.ca/2024/06/civ4-ui/scaledpy-crop.jpg
A froghat.ca/2024/06/civ4-ui/steamdeck.png
A froghat.ca/2024/06/civ4-ui/vanilla-1024x768.jpg
A froghat.ca/2024/06/civ4-ui/vanilla-3840x2160.jpg
A froghat.ca/2024/06/civ4-ui/x32dbg-rgba.jpg
M static/css/meme.sass
M froghat.ca/2024/05/grounded-mumble/grounded-mumble.rst => froghat.ca/2024/05/grounded-mumble/grounded-mumble.rst +1 -1
@@ 55,7 55,7 @@ We can find the address manually the first time; but, after the game restarts, t
Instead of having a consistent address for your player's location, the game works by having a consistent address for another structure that has the address for your player's location. Or, with additional indirection, has a consistent address to a structure that points to *another* structure that points to player's location.
It's a treasure hunt where you aren't given the directions up-front -- you are given directions to find your next directions.

So it's not good enough just to find the address of the positional data once. We need to back-track and figure out what structures are pointing to the positional data and what structures are pointing to them and and what have addresses consistent through restarting the game. That way, we can write a program that reads those structures in sequence and follows them like a treasure hunt.
So it's not good enough just to find the address of the positional data once. We need to back-track and figure out what structures are pointing to the positional data and what structures are pointing to them and what have addresses consistent through restarting the game. That way, we can write a program that reads those structures in sequence and follows them like a treasure hunt.

Pointer Scan
------------

A froghat.ca/2024/06/atlast/billboard-reith.png => froghat.ca/2024/06/atlast/billboard-reith.png +0 -0
A froghat.ca/2024/06/atlast/billboard-vanilla.png => froghat.ca/2024/06/atlast/billboard-vanilla.png +0 -0
A froghat.ca/2024/06/atlast/cat.jpg => froghat.ca/2024/06/atlast/cat.jpg +0 -0
A froghat.ca/2024/06/atlast/gamefont-reith.png => froghat.ca/2024/06/atlast/gamefont-reith.png +0 -0
A froghat.ca/2024/06/atlast/gamefont-vanilla.png => froghat.ca/2024/06/atlast/gamefont-vanilla.png +0 -0
A froghat.ca/2024/06/atlast/index.rst => froghat.ca/2024/06/atlast/index.rst +159 -0
@@ 0,0 1,159 @@
HTML as a configuration file format
===================================

:summary: Another Civilization IV post disguised as a revolutionary way to marry bloated webapps with software configuration files.
:date: june 7 2024

This post is an excuse to talk about Civilization IV again.
The title is clickbait, so here is a summary.

Firefox lets you save web pages through a menu button labeled *Save Page As...* or by pressing `Ctrl+S`.
This saves the current value of `<input>` fields on the page to an HTML file [#]_.
When you open the saved page in your web browser, the saved values are right there.
It's like editing any regular file on your computer. Open it, modify it, and save it -- when you reopen the file later, the modified values are still there. *So retro.*

.. aside::
.. [#] As far as I know, Chrome does not do this. It seems to save some changes to the DOM, but not values on `<input>` elements.

A document like this is interesting because it can be data for another program and, compared to a typical configuration file, a more graphically rich way to edit & interact with the data in the file.

Software configuration files often use a format with a simple syntax that resembles plain text -- and there are plenty of good reasons to do that.
But modern web browsers can show images, have built-in calendars and time pickers or widgets for other data types, and can run JavaScript that can be used to
show visualizations based on provided values or extend interactivity in other ways (like show popups and cookie consent banners).
Used with discretion, some users might find this a more approachable and useful editing experience than editing text files.

So the pitch here is that, instead your program reading
configuration from keys and values in an INI, TOML, or JSON file or something, it
reads an HTML file and uses the values of `<input>` elements. Or, get real wacky with it and use other parts of the document, like images from `<img>` elements.
And design the HTML file to make use of the browser to provide a rich editing experience for modifying the document itself.

In the rest of this blog post, I talk about an example of this kind of thing in a tool I made
as part of a small edit to a turn based strategy game from 2005, Civilization IV.

GameFont.tga
------------

Most of the text in Civilization IV is rendered from a TTF file. It's easy to modify the game's files to use a different font or, since the font is made of vectors, draw text at a larger font size. It's like how fonts work everywhere else -- like in a word process or web browser.

But the text rendered on city bar comes from an atlas called *GameFont.tga*.

.. figure:: billboard-vanilla.png
   :target: billboard-vanilla.png

   This is what a city looks like in game. The city bar is the graphic at the bottom with the numbers on it; showing the city status line (like religions present), the city size, the city name, and the current production.

.. figure:: gamefont-vanilla.png
   :target: gamefont-vanilla.png

   This is the vanilla GameFont.tga file. (Beige checkerboard added in to show transparency.) The text used in rendering the city bar above is taken from this file.

If you change the *GameFont.tga* image, you can change the text shown in city bar.

.. figure:: billboard-reith.png
   :target: billboard-reith.png

   The city bar shown again but using *BBC Reith*. (I think it's also bold?) The change is a bit subtle.

.. image:: gamefont-reith.png
   :target: gamefont-reith.png

To help do this, there are two programs.

- One, to generate just the text portion of the atlas in a new font.
- Another, to unpack the atlas as individual images and repack it.

Both programs are in a project on GitHub at `sqwishy/civ4-atlast <https://github.com/sqwishy/civ4-atlast>`__. They both use HTML in different ways.

atlast.html
-----------

The text portion of the atlas is output from a JavaScript program in an HTML file. It uses `<input>` fields to provide user options, a `<canvas>` to show a preview of the atlas image, and a button to save the atlas as a TGA image file onto the user's computer.

Since it's a single HTML file, GitHub serves it at `sqwishy.github.io/civ4-atlast/atlast.html <https://sqwishy.github.io/civ4-atlast/atlast.html>`__.

It isn't configuration for a separate executable. Instead, the `<input>` values that Firefox preserves when you use `Save Page As...` is state for the program in the same HTML file.
One reason this works is that the inputs are the source of truth for the program. When the JavaScript program runs, it uses the values of the inputs as parameters. And those values are saved and loaded in the HTML by Firefox.

The main reason to use a web browser to generate the text atlas was that it does a pretty good job of rendering text and offers APIs for font metrics.
But, since web browsers are prolific, there are other advantages -- like even just not having to deal with Windows telling users that your program is dangerous because you didn't pay your $150 indulgence to the tech-papacy to get the executable signed.

Below is a zoomed in portion of the vanilla *GameFont.tga* file but with transparency removed. So every pixel is fully opaque.

.. figure:: zoomed-noalpha.png

Some cells appear entirely white because the transparency has been removed. The white cells in the bottom half are just empty. But, the first four rows are white text on a white *transparent* background -- they appear fully white here because the transparency is removed for this illustration.

It is a bit weird to think of transparency as having a colour, like *white* transparent. Or like the *pink* and *cyan* transparent pixels in the frame between cells.
Each pixel has four components: red, green, blue, alpha. The alpha channel specifies how translucent the pixel is.
A pixel is transparent when the alpha channel is zero, but it may still have other values for the three other components.
Nevertheless, in most programs, those transparent pixels are displayed the same regardless of the values of the three other components.
So we don't develop an intuition that transparent pixels have colour values.

.. aside::

   It's also incredibly frustrating trying to edit a transparent image in GIMP. If you want to change the colour of a pixel you can use the pencil tool, but the colour of the pencil doesn't have an alpha channel so it draws opaque. You can set the opacity of the pencil, but if you set the opacity to zero the pencil won't do anything -- it won't change the red, green, blue components. You can set it to a very low non-zero value like 0.1, and that works to pencil over already transparent pixels but not opaque pixels. The pencil opacity isn't setting the alpha -- it's changing the strength of the pencil affect. The metaphores of pencils and stuff in image editing tools doesn't quite mesh with how we're trying to edit this image. So that's one reason that I wrote a program to help with this.

Each character or icon is in a cell. Cells are separated by a *pink* border. In the border the right of some cells is a *teal* pixel that specifies the baseline, used for characters like `g`, `j`, and `p` that have sticky-downy bits (descenders_) below the baseline. As far as I can tell, Civilization IV reads these cells in sequence to map them with whatever glyph they represent. For example, the fourth cell is a dollar sign `$`. If city's name contained *$*, the game would use whatever image is in that fourth cell when rendering the dollar sign in the city name in the city bar shown earlier.

.. _descenders: https://en.wikipedia.org/wiki/Descender

To summarize, the *atlast.html* file uses a browser canvas to generate a TGA image of the text (letters, numbers, symbols) portion of the *GameFont.tga* atlas based on the user's parameters including font weight, size, family, color.
Since the TGA image this generates follows the structure of the original *GameFont.tga*, shown above with pink frames, it can be used as input for a program designed to work with the original *GameFont.tga* -- the next program.

atlast.exe
----------

The second program is a command line tool for unpacking an atlas like *GameFont.tga* into individual images & repacking the images back into a single atlas.

It creates a manifest to store information about the atlas other than the image data of the cells themselves. This includes the sequence of cells in each and the sequence of rows in the image.

It looks something like this:

.. code:: html

   <div data-atlas>
     <div data-atlas=row>
       <img src='000.png'>
       <img src='001.png'>
       <img src='002.png'>
       <img src='003.png' data-descent=2>
       ...
     </div>
     <div data-atlas=row>
       <img src='055.png'>
       <img src='056.png'>
       <img src='057.png'>
       <img src='058.png' data-descent=5>
       ...
     </div>
     <div data-atlas=row>
         ...
     </div>
     ...
   </div>

Cells are in sequence under a `data-atlas=row` and those rows are in sequence under a `data-atlas`, the top-level object for the atlas in that document.

Each cell in the manifest, an `<img>` element, has a file path to the unpacked image of each cell in the `src` attribute. It may include the descent/baseline marker position (as `data-descent=...` in the file) because that is needed to accurately reconstruct the atlas and the unpacked images do not include the transparent frame where that marker occurs.

That manifest is an HTML file. Also in that HTML file is a stylesheet that makes the manifest *resemble* the atlas when you open it in a browser. Here is a screenshot of a manifest viewed in Firefox -- the is manifest generated from the vanilla *GameFont.tga*.

.. figure:: manifest.png

Viewing the manifest in the browser doesn't really help with editing it -- except in so far as you can preview changes you make to the manifest from another program like a text editor.
If you wanted to be really fancy, I'm pretty sure it's possible to include a JavaScript program on the page to add, modify, or remove cells through DOM manipulation. In Firefox, and even Chrome, it seemed that the *Save Page As...* feature would changes to the DOM made from JavaScript.

Originally, I was using a TOML file for this manifest; it's simpler and more conventional. But, I needed the program that unpacks the atlas to be able to modify an existing manifest and do a sort of in-place update instead of overwriting it. It was also important that it did not unnecessarily tamper with whitespace or comments that the user may have added to the manifest. The libraries for doing that in TOML were not especially remarkable and using them would be at least as much work as using the Rust library `tl`_ to write the manifest in HTML. And, with HTML, there is the added bonus of using the browser to get a nice preview of the manifest.

.. _tl: https://crates.io/crates/tl

Using these two tools, it's fairly straightforward to unpack the game's original *GameFont.tga* into separate images, make your own TGA text atlas from *atlast.html* using your web browser, unpack it over top of the unpacked vanilla atlas, and repack the separate images into a new *GameFont.tga* that the game can use.

.. class:: full-bleed
.. figure:: cat.jpg

   `cat by kernpanik <https://chaos.social/@kernpanik/111843871971503023>`__

So next time you're writing a new micro-service -- or whatever it is you do bring meaning to your singular mortal journey through time -- and you have to decide between INI, XML, JSON, JSONC, YAML, TOML -- or whatever wacky format they think of next -- consider instead using HTML for your configuration file and throwing a bloated webapp inside of it.

*If you want to read more wacky Civilization-related content, see my follow-up post;* `Exploding the Civilization IV User Interface <../civ4-ui>`__.

A froghat.ca/2024/06/atlast/manifest.png => froghat.ca/2024/06/atlast/manifest.png +0 -0
A froghat.ca/2024/06/atlast/zoomed-noalpha.png => froghat.ca/2024/06/atlast/zoomed-noalpha.png +0 -0
A froghat.ca/2024/06/civ4-ui/better-shadow.jpg => froghat.ca/2024/06/civ4-ui/better-shadow.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/bibracte-shadow.jpg => froghat.ca/2024/06/civ4-ui/bibracte-shadow.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/bigfont-3840x2160.jpg => froghat.ca/2024/06/civ4-ui/bigfont-3840x2160.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/chaiNNer-lm.webp => froghat.ca/2024/06/civ4-ui/chaiNNer-lm.webp +0 -0
A froghat.ca/2024/06/civ4-ui/chaiNNer.webp => froghat.ca/2024/06/civ4-ui/chaiNNer.webp +0 -0
A froghat.ca/2024/06/civ4-ui/citybar-cozy.jpg => froghat.ca/2024/06/civ4-ui/citybar-cozy.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/citybar-toosmol.jpg => froghat.ca/2024/06/civ4-ui/citybar-toosmol.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/citydetails-3840x2160.jpg => froghat.ca/2024/06/civ4-ui/citydetails-3840x2160.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/civ5bar.jpg => froghat.ca/2024/06/civ4-ui/civ5bar.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/contrast.jpg => froghat.ca/2024/06/civ4-ui/contrast.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/crunchy-shadow.jpg => froghat.ca/2024/06/civ4-ui/crunchy-shadow.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/final-3840x2160.jpg => froghat.ca/2024/06/civ4-ui/final-3840x2160.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/index.rst => froghat.ca/2024/06/civ4-ui/index.rst +374 -0
@@ 0,0 1,374 @@
Exploding the Civilization IV User Interface
============================================

:summary: Making the Civilization IV look big and silly for high pixel density displays.
:date: june 8 2024

*This is a sequel to the previous post on this blog;* `HTML as a configuration file format <../atlast>`__.

When Civilization IV was released, 2005, people generally had smaller computer screens and lower resolutions than what we use on desktop computers today. 

Steam, an app store for computer games, has a survey that samples the hardware of some of their users. In 2008_, the most common primary display resolutions were **1024x768** at **25%** adoption, **1280x1024** also at **25%**, followed by **1680x1050** at **14%**. Compared to a `more recent survey in 2024`_, with **1920x1080** at **58%**, followed by **2560x1440** at **20%**.

.. _2008: https://web.archive.org/web/20081214160840/http://store.steampowered.com/hwsurvey/
.. _more recent survey in 2024: https://web.archive.org/web/20240407193236/https://store.steampowered.com/hwsurvey/

Resolutions have increased but so have display sizes. These days days, we have more pixels on our monitors, but they tend to be larger monitors. The *pixel density* of both a **24" 1920x1080** display and a **32" 2560x1440** is **92** :abbr:`PPI (pixels per inch)`. 
Though, it's not uncommon to find **27" 2560x1440** displays with a PPI of **109**.

In olden times, you could get **92** PPI from a **14" 1024x768** display or **96** PPI on a **17" 1280x1024** display.
Or **98** PPI from a **15.4" 1280x800** display you might find in a laptop.

This is a monitor recommendation published in a 1995 issue of *PC Magazine* for a **17" 1280x1024** display.

.. class:: full-width
.. picture:: monitor-1994-lm.jpg
   :alt: A brief review of a flat screen CRT monitor, the Nokia Multigraph 447X, from a 1995 issue of PC Magazine. The text includes, "... users have begun to realize that a 17-inch display id ideally suited for working in the Windows environment. ... The Multigraph 477X offers 1,280-by-1,024 resolutionh at a flicker-free 76-Hz refresh rate and 1,024-by-768 resolution at 95 Hz." 

   .. source::
      :srcset: monitor-1994-dm.jpg
      :media: (prefers-color-scheme: dark)

Admittedly, pixel density hasn't really changed significantly for *most* people. Particularly on the desktop.
A **4K (3840x2160) 27"** display has a pixel density of **163** PPI. But that isn't common.
And `my phone <https://en.wikipedia.org/wiki/OnePlus_5>`__ has a **5.5" 1920x1080** display with a density of **401** PPI.
But who needs to play Civilization IV on their phone?

The issue here is that a game or application rendering text at a specific size in pixels will produce text that is physically smaller, and more difficult to read, on the 4K display compared to a display with ninty-something PPI. 

Often, software has ways to detect display density and scaling preferences and render larger text and scale up other parts of the user interface appropriately.
That's why the text on your phone isn't so insanely small that you can't read it.
And although games have historically lagged behind the rest of software in supporting higher pixel densities, it's common *these days* to get pretty good support for higher PPI displays in games.

And for good cause. Even though 4K displays are still not common, there seems to be a growing market for handheld gaming devices.
Like the `Steam Deck`_ with **216** PPI at **7" 1280x800**,
the `Asus ROG Ally`_ with **315** PPI at **7" 1920x1080**,
and the `Lenovo Legion Go`_ with **343** PPI on its **8.8" 2560x1600** display.

.. _`Steam Deck`: https://en.wikipedia.org/wiki/Steam_Deck
.. _`Lenovo Legion Go`: https://en.wikipedia.org/wiki/Lenovo_Legion_Go
.. _`Asus ROG Ally`: https://en.wikipedia.org/wiki/Asus_ROG_Ally

.. figure:: steamdeck.png

   A photo of a Steam Deck.

These devices aim to run games that were designed for the Windows desktop, either by running the Windows operating system itself or by running Linux and using an "emulation" layer like Proton.
This is in contrast with a smartphone, where games are designed with the expectation that they're being played on a smartphone sized touch screen.

So, as handheld gaming hardware become more of a thing, game developers shipping for desktop computers running Windows or Linux have the opportunity to reach a market of handheld gaming users if they can make their game enjoyable on higher PPI displays.

I should point out that these handheld gaming devices are intended to be closer to your face than a typical monitor. So there's an argument that the higher pixel density doesn't matter quite so much. And this might be true for the Steam Deck. But for other devices, with PPI in the three hundreds, I think the pixel density is high enough that games need to account for it. But that's just my guess, I haven't used any of these devices personally. When someone buys one for me, I will run Civilization IV on it and update this post.

upscaling the hard way
----------------------

Civilization IV supports a display resolution as low as **1024x768**. That is 38% of **1920x1080**.

.. class:: full-width
.. picture:: rectangles-lm.svg

   .. source::
      :srcset: rectangles-dm.svg
      :media: (prefers-color-scheme: dark)

Much of the user interface in Civilization IV is laid out around the edges of the display; with the center reserved mostly for viewing the world map, with occasional popup or dialog screen.
The code has special cases about how to lay out the interface at 1024x768. Beyond that, the layout scales by increasing the distance between user interface elements, along the corners and edges, rather than increasing the size of those elements. This makes a lot of sense if the physical size of the display increases with resolution -- which it has. But if you want to play Civilization IV on a display with high pixel density, then text, buttons, and other graphics might start to look uncomfortably small.

In the previous post, I mentioned that most of the game's font is rendered from a TTF file.
And since a TTF describes a font as vectors, like paths rather than pixels, software can render these fonts at any size reasonably well.
The game has *theme* files that tell the game what font file to load and what font size to to use. 
It's easy to modify the theme file to use a different font file or to draw it at a larger size.

But this doesn't affect anything using *GameFont.tga*; including the city bars shown earlier and icons shown inline with the TTF text.

But, using the tooling mentioned in the previous post, we can unpack the vanilla *GameFont.tga* atlas, increase the size of the individual icon images, and repack it with own text atlas rendered at a larger font size.

.. class:: full-width
.. figure:: vanilla-1024x768.jpg
   :target: vanilla-1024x768.jpg

   This is a screenshot of the game at it's minimum resolution, 1024x768.

.. class:: full-width
.. figure:: vanilla-3840x2160.jpg
   :target: vanilla-3840x2160.jpg

   This is a screenshot of the game at 4k, 3840x2160.

.. class:: full-width
.. figure:: bigfont-3840x2160.jpg
   :target: bigfont-3840x2160.jpg

   This is a screenshot after switching fonts and increasing the size quite a bit. On a 27" display, this text is larger than what you probably want. But its exaggerated for the demonstration.

The game ships with version 2.4 of the CPython interpreter and includes Python bindings to parts of the game systems so that user interface screens can be written in Python.
So a lot, but not all, of the vanilla user interface is written in Python and available for modification. But it's also written with a lot of fixed sizes. Modifying that code to draw the interface based on a scaling factor would require many changes. And you would need to understand the code to do a good job. And there are thousands of lines that look like this ...

.. class:: full-width reflow
.. code:: python

   screen.addTableControlGFC( "SelectedCityText", 3, 10, yResolution - 139, 183, 128, False, False, 32, 32, TableStyles.TABLE_STYLE_STANDARD )

... or like this ...

.. class:: full-width reflow
.. code:: python

   iBtnX = xResolution - 284
   iBtnY = yResolution - 177

   iBtnW	= 22
   iBtnWa	= 20
   iBtnH	= 24
   iBtnHa	= 27

   i = 0
   szButtonID = "Emphasize" + str(i)
   screen.addCheckBoxGFC( szButtonID, "", "", iBtnX, iBtnY, iBtnW, iBtnH, WidgetTypes.WIDGET_EMPHASIZE, i, -1, ButtonStyles.BUTTON_STYLE_LABEL )

   ...

   i+=1
   szButtonID = "Emphasize" + str(i)
   screen.addCheckBoxGFC( szButtonID, "", "", iBtnX+iBtnW, iBtnY, iBtnWa, iBtnH, WidgetTypes.WIDGET_EMPHASIZE, i, -1, ButtonStyles.BUTTON_STYLE_LABEL )

   ...

   i+=1
   szButtonID = "Emphasize" + str(i)
   screen.addCheckBoxGFC( szButtonID, "", "", iBtnX+iBtnW+iBtnWa, iBtnY, iBtnW, iBtnH, WidgetTypes.WIDGET_EMPHASIZE, i, -1, ButtonStyles.BUTTON_STYLE_LABEL )


It's the sort of thing where, to do a good job, you wouldn't just add a bit of number scaling math in-line to this. It's already mucky -- difficult to read and think about.
Ideally, you would use the opportunity to change how these interfaces are coded to improve readability [#]_.
    
.. class:: aside
.. [#]
    That's even what one mad-lad, f1pro, did as part of their Civilization IV mod, `Advanced Civ`_.
    They made abstractions from the patterns in the vanilla code -- of laying
    out buttons and widgets next to each other, or repeated in sequence, or in
    a grid. And described the interface using those concepts instead
    of the noise of pixel-pushing math. They even have it all `on GitHub`_.

    To top it off, they have a manual describing every change the mod
    introduces; complete with a rationale, references to relevant forum posts,
    comparisons to behaviours in vanilla or other mods, areas for future
    improvement, and more. The document is available as a six hundred and forty
    two page PDF. And that page count is on A4 paper; taller than north
    american letter paper!
 
    .. _`Advanced Civ`: https://forums.civfanatics.com/resources/advanced-civ.26111/ 
    .. _`on GitHub`: https://github.com/f1rpo/AdvCiv/blob/master/Assets/Python/Screens/CvMainInterface.py

One drawback of a big rewrite like that, as tempting as rewrites can be, is that any mods that extended the vanilla user interface and repeated the patterns in it would also need to be adjusted to work with a scaling factor.
So, lacking in any constitution whatsoever, I opted for a very lazy way of rescaling the user interface.

The game's API for building a user interface in Python is done with one class called CyGInterfaceScreen_.
The class is a container for all sort of stuff; getting the resolution, adding and showing buttons, or checkboxes, comboboxes, tables.
Many methods take a number of pixels as parameters, like for the position or size of widgets. Some methods return a number of pixels, for things like the width or resolution of the game window.

I made a *fake* CyGInterfaceScreen, with all the same methods as the real one, that forwards each method call to the real CyGInterfaceScreen, but fudges the numbers a little bit to trick the interface code into thinking that our display as a lower resolution than it really is.
Swap out the real CyGInterfaceScreen for this fake one and, whenever something asks it the resolution of the display, it will ask the real CyGInterfaceScreen for the resolution but then *divide* it some factor when returning the value. And if it's asked to put a button at a specific location, it will *multiply* the coordinates by that same factor before passing it along to the real CyGInterfaceScreen.

.. _CyGInterfaceScreen: https://civ4bug.sourceforge.net/PythonAPI/
.. _archive.org: https://web.archive.org/web/20231106050220/https://civ4bug.sourceforge.net/PythonAPI/

.. class:: full-width
.. figure:: scaledpy-3840x2160.jpg
   :target: scaledpy-3840x2160.jpg

   This is the result of scaling through this fake Python class using a factor of two.

Admittedly, this result isn't perfect. But it was very easy to do. There are a lot of places where the scaling doesn't have an affect because the sizes don't go through this the CyGInterfaceScreen in Python; they're defined in a theme file somewhere -- in the same general area where we changed the font size, in fact.

In the corner of that screenshot we see an example of how the game renders icons from *GameFont.tga* inline with text the TTF font.

.. figure:: scaledpy-crop.jpg
   :target: scaledpy-crop.jpg

   Here, we're using an enlarged TTF font, but the icons,
   found in the *GameFont.tga*, still unmodified and at their original size.

We can unpack *GameFont.tga*, increase the scale of the icons, and repack it.

.. figure:: scaledgamefont-crop.jpg
   :target: scaledgamefont-crop.jpg

   Unfortunately, there's a funny thing where the icons are drooping below the text. I couldn't figure that one out. And the line spacing is pretty dramatic.

Remember the city bar from the previous post? It uses the *GameFont.tga* for its text and icons. But the rest of the city bar graphic come from other textures and a 3D model.

.. So more adjustments are needed to accommodate the larger text and icons. Or else it just looks kinda squished; shown later.

Civilization IV was built on an engine called Gamebryo_. The same engine Bethesda used in for some of their '00 releases, Morrowind, Oblivion, and Fallout 3. Bethesda later used Gamebryo as a basis for their `Creation Engine`_ in their following titles like Skyrim.

So, NifSkope, a tool still used today to
edit files for 3D information in Fallout 4 and Skyrim, is
also used to edit the same kind of files in Civilization IV for things like
unit graphics. Or the city bar.

.. _Gamebryo: https://en.wikipedia.org/wiki/Gamebryo
.. _Creation Engine: https://en.wikipedia.org/wiki/Creation_Engine

.. class:: full-width
.. figure:: nifskope.jpg
   :target: nifskope.jpg

   This is the new citybar model in NifSkope.

.. figure:: citybar-toosmol.jpg
   :target: citybar-toosmol.jpg

   The original citybar model with the larger *GameFont.tga*; it looks squished.

.. figure:: citybar-cozy.jpg
   :target: citybar-cozy.jpg

   The new citybar model; much roomier.

Part of this change was moving and rotating the growth and production progress bars. In vanilla, they are horizontal behind the city name and production text. This can cause readability issues, for the city name in particular, as the bar fills up and reduces contrast between the text and the colour of the progress bar itself. 

.. aside::
.. figure:: contrast.jpg
   :target: contrast.jpg

   An example of a contrast issue with the vanilla city bar.
   The city name, *Constantinople*, is much harder to read against the filled city growth progress bar than compared to the mostly empty production progress bar behind the text *Forge*.

I borrowed from how Civilization V (the best looking Civilization game so far) does their city bars; shown below. My edit certainly isn't as spiffy, but it's an improvement of sorts.

.. aside:: Also worth mentioning is a mod called `Civilization IV Remaster <https://forums.civfanatics.com/threads/civilization-iv-remaster.671590/>`__ that changes some assets including the city bar to resemble Civilization VI. Before seeing this, it hadn't even crossed my mind that the city bar was a model that you could easily edit in NifSkope.


.. figure:: civ5bar.jpg
   :target: civ5bar.jpg

Below is the result of the main screen after all these changes. It's pretty messed up, honestly. It's a bit tragic.

.. class:: full-width
.. figure:: final-3840x2160.jpg
   :target: final-3840x2160.jpg

When you click in to a city, it this is what it looks like.

.. class:: full-width
.. figure:: citydetails-3840x2160.jpg
   :target: citydetails-3840x2160.jpg

It's a bit silly, but it's also not totally unplayable. There is a lot of room for improvement. Some are simple changes to sizes defined in theme files.
Other stuff I don't understand, like the extreme whitespace above and below lines, though it seems to vary with the font used.
I think the largest and most looming mystery so far is why the icons aren't properly aligned vertically in the text.

Furthermore,
some user interface elements don't scale with the techniques covered here so far; either through theme files, font size, or the CyGInterfaceScreen. One of those are the *resource bubbles*; little pins on the map that help point out important resources. f1rpo, mentioned earlier as the author of the *Advanced Civ* mod, indicated in `this thread on civfanatics.com <https://forums.civfanatics.com/threads/shrinkingis decreased.bles-by-modifying-the-exe.676727/>`__ that there are instructions in the game executable that can be modified to affect the size of these resource bubble pins. 

It's possible to do this modification at runtime without changing the game executable on disk; since, when the game is run, its program is copied into a memory and it can be modified in memory instead of on disk.

Below are some old screenshots that shows the modification being made manually through a debugger.
The instruction being executed there is `push 42.f`. 
The value pushed on to the stack by the `push` instruction is displayed as a floating point in the bottom left of the debugger; under the *dump* pane.
In this example, reducing the number pushed on the stack, from *42* to *22*, shrinks the bubble, shown on the left edge of both screenshots.

.. class:: full-width
.. figure:: pin-normal.jpg
   :target: pin-normal.jpg

.. class:: full-width
.. figure:: pin-smol.jpg
   :target: pin-smol.jpg

It should be pretty easy to make this kind of change from the game itself, instead of manually from a debugger.
The `ctypes`_ package in Python's standard library would be great for this.
Unfortunately, ctypes was added in Python 2.5 and the game ships with version 2.4.
So I had a go at writing a C extension, importable in Python 2.4, to help read and write to arbitrary memory regions.

It's very simple. It has functions for getting and setting various types of values. So we could accomplish what was shown in the debugger above by writing the following:

.. code:: python

    PlotPinScale.setFloat(0x465059, 22.0)

It also lets you pass in a format string from the `struct`_ package in Python to read or write a tuple of values, instead of one value at a time.

Remember the contrast issue with the white text on that orange background on the city bar? One thing you can do with the text generated from the browser's canvas API, in *atlast.html* from the previous post, is add an outline and a text shadow. That helps a lot with the contrast.

.. _ctypes: https://docs.python.org/2.7/library/ctypes.html#module-ctypes
.. _struct: https://docs.python.org/2.7/library/struct.html#module-struct

.. figure:: crunchy-shadow.jpg
   :target: crunchy-shadow.jpg

Unfortunately, this makes the city size, the big number "9" on the left of the image, look super crunchy. This is because it's a *black* nine with a *black* shadow.

The colours used for the city bar here are in a function in a file called `cvgamecoredll.dll`. The source for that DLL was made public by the Civilization developers in order to allow modders to do more stuff. That's really cool. We could modify the source code to change the colour of the number nine and recompile the DLL. But I don't know how work a C compiler on Windows. And I'm too old and full of micro-plastics to learn new things.

If we open up that module in x64dbg, we can see instructions moving the colour black on to the stack. 
When viewing the player's cities, like the one shown in the screenshot earlier, this function copies those values from the stack to a return value later on.

.. class:: full-width
.. figure:: x32dbg-rgba.jpg
   :target: x32dbg-rgba.jpg

The memory address of this instruction is `03d2660d`.
Though, since these instructions are located in the `cvgamecoredll.dll` module, and the base address of that module may be different between launches of the game executable, those instructions won't always be at that address.

.. class:: full-width
.. code:: python

    movr, r, movg, g, movb, b = \
        PlotPinScale.getValues(0x03d9660d, "=4sf4sf4sf")

    assert movr == "\xc7\x44\x24\x14"

    PlotPinScale.setValues(0x03d9660d, "=4sf4sf4sf",
        movr, 1, movg, 1, movb, 1)

This uses a struct format string to tell Python that we want to read the memory as a tuple of six values.
Each line, highlighted in the image above, is eight bytes.
The first four bytes of the instruction are read as a string in Python; `4s`.
The other four bytes, for the instruction source operand, is translated as a float; `f`.
We do that three times, once for each *R* *G* *B* component, and use `=` to specify alignment to get `=4sf4sf4sf`.

Since the instructions are unpacked as strings, we can compare them to strings in Python to check that the instructions at the address match what we expect. That's the `assert` statement in the illustration above.

Finally, we can overwrite that memory with new instructions that use the colour white instead of black by setting the source operand of each instruction to `1` instead of `0`.

.. figure:: better-shadow.jpg
   :target: better-shadow.jpg

   This is the result. The city size on the left of the graphic is white instead of black and the text shadow looks a bit less goofy.

It's not a complete solution. When viewing cities belonging to other empires, the empire colours are used. And you can end up with the same problem.

.. figure:: bibracte-shadow.jpg
   :target: bibracte-shadow.jpg

   The city size text for *Bibracte* is coloured dark green and it looks a bit worse *with* a text shadow than it would without.

.. aside:: Also, I just realized that the mod *Realism Invictus* has a *GameFont.tga* that uses a text shadow *except* for the numbers. Actually brilliant.

But I just needed an excuse to show an example of using the PlotPinScale module to unpack and pack multiple Python values at a time from a single read or write.
And, again, a mod author interested in doing this would just change the `cvgamecoredll.dll` source code and recompile it.

`I included the Python module's source code along with a post I made about the topic on the forums at civfanatics.com <https://forums.civfanatics.com/threads/shrinking-resource-bubbles-by-modifying-the-exe.676727/post-16488597>`__.

.. class:: full-bleed
.. figure:: meow.jpg

   `cat by Cloudtail the Snow Leopard <https://www.flickr.com/photos/blacktigersdream/28419866096/>`__

I was going to put something here about how video game modding is an interesting social activity for cultured and sophisticated persons with refined tastes -- and somehow not just a outlet for the crazy, obsessed, and fixated. But I couldn't figure out how to make that be words. And maybe it isn't even true.

It's just that, sometimes, someone will go outside, take a picture of bark on a tree or some rocks, and then import it into the video game so that the video game trees and rocks look nicer than they were before. And do the same thing for snow-covered trees, and spend time trying to make the snow on their tree match everybody else's snow texture. Which is futile because there are a dozen different snow textures and making everyone happy is not gonna happen, especially if they're the sort of people who are hand-picking their snow textures.

But people do it anyway for some reason.

And sometimes someone makes something that you didn't expect you'd enjoy and didn't know you wanted.

.. class:: full-bleed
.. picture:: chaiNNer-lm.webp

   .. source::
      :srcset: chaiNNer.webp
      :media: (prefers-color-scheme: dark)

A froghat.ca/2024/06/civ4-ui/meow.jpg => froghat.ca/2024/06/civ4-ui/meow.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/monitor-1994-dm.jpg => froghat.ca/2024/06/civ4-ui/monitor-1994-dm.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/monitor-1994-lm.jpg => froghat.ca/2024/06/civ4-ui/monitor-1994-lm.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/nifskope.jpg => froghat.ca/2024/06/civ4-ui/nifskope.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/pin-normal.jpg => froghat.ca/2024/06/civ4-ui/pin-normal.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/pin-smol.jpg => froghat.ca/2024/06/civ4-ui/pin-smol.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/rectangles-dm.svg => froghat.ca/2024/06/civ4-ui/rectangles-dm.svg +4 -0
@@ 0,0 1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" height="100%" version="1.1" viewBox="0,0,512,288" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[{'fg': '#f9f5d7'}        text   { fill: #f9f5d7; font-family: sans-serif; text-anchor: end }
path   { fill: transparent; stroke: #f9f5d7; stroke-width: 1px }
]]></style></defs><path d="M 7.433639 8.099348 C 89.927742 6.780837 177.970017 8.299573 268.877013 8.466566 M 264.810104 4.002643 C 267.485527 69.649392 269.168302 135.944726 268.175197 199.513379 M 268.802314 198.811825 C 179.759408 199.433162 93.494586 202.858848 6.946452 199.350229 M 5.586630 198.085945 C 6.587939 135.110872 5.079764 67.865905 5.644104 6.941310" /><text x="260.6666666666667" y="194.0">1024x768</text><path d="M 5.071557 5.085518 C 168.332973 8.093371 332.314380 4.384976 500.387258 5.931497 M 500.790137 6.409448 C 498.359765 95.296572 502.853520 191.267926 504.115119 277.968969 M 498.041949 281.690667 C 335.640374 285.331484 171.668650 279.820910 6.775893 279.879644 M 7.530329 282.525355 C 4.851761 192.198182 1.692464 98.067607 5.411325 4.184932" /><text x="494.00000000000006" y="275.25">1920x1080</text></svg>
\ No newline at end of file

A froghat.ca/2024/06/civ4-ui/rectangles-lm.svg => froghat.ca/2024/06/civ4-ui/rectangles-lm.svg +4 -0
@@ 0,0 1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<svg baseProfile="full" height="100%" version="1.1" viewBox="0,0,512,288" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"><![CDATA[{'fg': '#1d2021'}        text   { fill: #1d2021; dominant-baseline: middle; font-family: sans-serif; text-anchor: end }
path   { fill: transparent; stroke: #1d2021; stroke-width: 1px }
]]></style></defs><path d="M 6.591985 2.397556 C 95.304572 5.447470 180.556582 8.167872 265.776226 7.300210 M 267.776323 6.551714 C 265.655538 69.861052 264.881849 133.821487 267.544852 200.096350 M 264.780367 202.488747 C 177.951650 199.768793 93.054613 200.613758 4.783142 200.875168 M 7.889346 201.073493 C 5.175000 134.035054 5.002310 69.078615 4.605890 5.054251" /><text x="262.6666666666667" y="192.0">1024x768</text><path d="M 3.593548 5.749480 C 171.629910 9.444753 332.092364 7.502436 499.389335 5.555564 M 496.675960 5.169676 C 503.068150 100.764224 500.995267 190.766725 500.355629 278.392950 M 497.030084 282.764687 C 338.481593 280.462248 169.587741 279.776494 4.891807 281.955635 M 3.694794 279.797741 C 7.207731 188.668919 3.733261 94.477091 3.686036 3.707423" /><text x="496.00000000000006" y="273.25">1920x1080</text></svg>
\ No newline at end of file

A froghat.ca/2024/06/civ4-ui/scaledgamefont-crop.jpg => froghat.ca/2024/06/civ4-ui/scaledgamefont-crop.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/scaledpy-3840x2160.jpg => froghat.ca/2024/06/civ4-ui/scaledpy-3840x2160.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/scaledpy-crop.jpg => froghat.ca/2024/06/civ4-ui/scaledpy-crop.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/steamdeck.png => froghat.ca/2024/06/civ4-ui/steamdeck.png +0 -0
A froghat.ca/2024/06/civ4-ui/vanilla-1024x768.jpg => froghat.ca/2024/06/civ4-ui/vanilla-1024x768.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/vanilla-3840x2160.jpg => froghat.ca/2024/06/civ4-ui/vanilla-3840x2160.jpg +0 -0
A froghat.ca/2024/06/civ4-ui/x32dbg-rgba.jpg => froghat.ca/2024/06/civ4-ui/x32dbg-rgba.jpg +0 -0
M static/css/meme.sass => static/css/meme.sass +1 -0
@@ 187,6 187,7 @@ main
    article
      .full-width
        margin-right: -($gap + $side)
        display: block

      /* automatically aside figcaption? */
      &            > figure:not(.full-width):not(.full-bleed) > figcaption,