# What the linter found in pak128

Run against the official pakset — `github.com/simutrans/pak128`, master, **2737 `.dat`
files** — with `simutrans_dat_lint.py` built from Simutrans 124.5.1's own descriptor
writers.

```
2737 file(s), 3 error(s), 74 warning(s)
```

**pak128 is in good shape, and one thing in it is not.** Most of what follows is a
dead key — something makeobj reads past and ignores — and is offered as tidying.

But the last section is not tidying. **Forty-nine lines in pak128 set a value the
engine never reads, and thirty of them say something different from the value it does
read.** A cannery its author dated to 1870 turns up in 1940. A glass factory
rebalanced from 800 down to 400 still produces 800. Nobody made a typo: the file
format quietly keeps the *first* of two answers and says nothing about the second.

Every finding is checked against the engine line that does or does not read it, and
the two that change the game are settled with makeobj — compiled twice, compared byte
for byte.

---

## Dead keys — makeobj ignores these

### `seasons=2` on 17 buildings — `material/raven_houses/urb3grand.dat`

`building_writer.cc` never reads a `seasons` key. It **counts** them, by probing for
image keys until one is missing:

```c
// building_writer.cc:266 - "scan for most number of seasons"
sprintf(buf, "%simage[%d][%d][%d][%d][%d][%d]", pos ? "back" : "front", l, y, x, 0, 0, season);
```

These houses do ship their season-1 images, so they work correctly — the writer finds
them on its own. `seasons=2` simply does nothing. (It is a `obj=tree` /
`obj=ground_obj` key, where it is real.)

### Four road-sign keys on a way-object — `material/guide_poles/Guide_Piles.dat:12-15`

```
min_speed=…    single_way=…    free_route=…    is_private=…
```

`way_obj_writer_t` reads none of them. They belong to `obj=roadsign`. Whatever they
were meant to do is not happening.

### `system_type` on a way-object — `infrastructure/catenary_all/catenary_old/CityCatenary.dat:74`

A `obj=way` key. A wayobj has `waytype` and `own_waytype`, and nothing of that sort.

### `max_weight` on a bridge — `material/monorail_track_old_official/monotrack.dat:95`

A `obj=way` key. `bridge_writer.cc` does not read it.

### `menupos` — `infrastructure/powerlines/way-power_lines-128.dat:58, 130`

On the tunnel and the bridge. There is no `menupos` .dat key anywhere in the engine.
The only `menupos` is `env_t::menupos`, a *simuconf.tab* setting for whether the
toolbar sits at the top or the bottom of the screen.

## `obj=program_text` — `pak128-program_texts.dat` (3 errors)

`program_text`, `menu_text` and `record_text` are not writer types, and makeobj
handles an unknown `obj=` by skipping it:

```c
// descriptor/writer/obj_writer.cc:46
obj_writer_t *writer = writer_by_name->get(type);
if (!writer) {
    printf("Skipping unknown %s object %s\n", type, name);
    return;
}
```

Confirmed by running makeobj on the file: `Skipping unknown program_text object Build
monorail depot`, and so on for all 24.

**This is not a pak128 bug.** The Makefile compiles an explicit list of directories
and never touches this file — it is a translation source, not a makeobj input. The
linter flagged it because it was pointed at the whole tree.

It is still worth knowing in general that an unknown `obj=` does **not** fail the
build. A typo there costs you the object, silently.

---

## A key said twice is a key said once — 49 lines, 45 files

This is the part worth reading. `tabfileobj_t::put()` keeps the **first** value and
drops the second on the floor:

```c
// dataobj/tabfile.cc:74
if(objinfo.get(key).str) { return false; }      // already there: give up
```

and one line above the call site, `tabfile.cc:854`, every key is lowercased first
(`// make lowercase`), so `Intro_year` and `intro_year` are the same key — the case
does not save you.

So a key written twice is dead text on the second line. makeobj says nothing. The
object compiles. And if you later go back and *edit* that second line — which is the
natural thing to do, because it is the one you can see — nothing changes, and there
is no way to find out why.

**Nineteen of the forty-nine simply repeat themselves** (`sound=-1` twice, the same
climate list twice) and cost nothing but a line. **Thirty say something different from
what the game obeys.** Those are below.

### The two that change the game

Both settled with makeobj, not by argument: each file was compiled as it ships, then
again with the *first* value changed, then again with the *second*. The `.pak` from
the third compile is **byte-for-byte identical** to the original — the second line
does not exist as far as the game is concerned.

| file | the game obeys | the dead line says |
|---|---|---|
| `factories/glass_factory.dat:44` | `OutputCapacity[0]=800` (line 24) | `=400` |
| `factories/cannery.dat:48` | `Intro_year=1940` (line 7) | `=1870` |

The glass factory is the sharp one. Somebody halved its output — a balance decision,
made on purpose — and the pakset has been producing 800 ever since, in every game
anybody has played. The cannery has the same shape: dated to 1870, available from
1940.

### The rest of the thirty

* **`factories/cornfield.dat:28`** — the engine takes `intro_year=2000` from line 7
  and ignores the `1600` on line 28. A cornfield that does not exist until the year
  2000. Same story in **`factories/raffinerie.dat:50`**: 1920, not the 1856 written
  below it.
* **`material/atocha_3-station/Atocha.dat:21`** — `level=7` wins; the `level=1` below
  is dead. Level drives the station's capacity, so this is not cosmetic either.
* **`vehicles/rail-engines/ceres.dat:19`** — `sound=3` wins and
  `sound=train-horn-steam-1.wav` is ignored. Somebody moved this locomotive from a
  numbered sound to its own horn, and the horn has never played.
* **`material/bridges_old/railtunnel.dat:24`** — `FrontImage[E]` is set to an image on
  line 20 and to `-` (nothing) on line 24. The image wins.
* **`vehicles/road-horses/horses.dat:16, 40, 64, 88`** — `waytype=road` on line 10,
  `waytype=bio` on line 16. **`bio` is not a waytype** — `get_waytype()` calls
  `dbg->fatal` on anything it does not recognise — and the horses work perfectly,
  precisely because the engine never sees that line. This is the file that taught us
  the rule: we first reported it as an *error*, and makeobj corrected us by compiling
  it happily.
* **Nineteen buildings whose climate list is not the one their author last wrote** —
  churches, schools, the observatory, the rugby stadium, the greenhouse, three
  industrial cityhouses. `special_buildings/city/no-winter/green_house.dat` is typical:
  the engine plants it in `temperate,tundra,rocky`, and the line below — the one that
  says just `temperate` — does nothing.

### What to do about it

Delete one of the two lines, on purpose, in each case. Which one is a judgement for
whoever knows what the object is meant to be: the engine has been obeying the first,
so keeping the first changes nothing in any existing game, and keeping the second is a
balance change that should be made deliberately rather than by accident.

---

## What the linter deliberately does NOT complain about

This part took longer than the findings, and it is the reason the list above is short
enough to read. The first run reported **3189 warnings and 123 errors**, and nearly
every one was the linter's fault, not the pakset's.

* **`.dat` keys can be a whole family.** `Image[n,e,s,w][0-1]` is eight keys, and the
  value may be an arithmetic expression over them
  (`tabfile_t::find_parameter_expansion`). Real paksets lean on this constantly. The
  subtlety is the leading dash: the engine's test is
  `(*s==',' || *s=='-') && *(s-1)!='['`, so `image[-]` — the ribi for "connects to
  nothing" — is a literal, not a range.

* **A comment after an image reference is legal**, and so is a leading `> `.
  `image_writer.cc:340`, the engine's own comment on the image syntax: *"after the
  dots also spaces and comments are allowed"*; the `> ` marks a non-zoomable image. A
  `#` only bites where the value is read as **text** — and then it bites hard:
  `freight=None  # a note` really does set freight to `"None  # a note"`, and the
  loader dies with `Cannot resolve 'GOOD-None  # a note'`.

* **Writers hand each other the whole object.** `factory_writer.cc:225` passes the
  `tabfileobj` to `building_writer_t`, so `obj=factory` reads every building key
  there is. That one accounted for 1622 false warnings on its own.

* **Some keys exist only as a char buffer.** This is the one that nearly got a false
  accusation into print. `factory_writer.cc:291`:

  ```c
  char str_smoketile[] = "smoketile[0]";
  ...
  str_smoketile[10] = '0' + i;                  // the key changes HERE
  pos_off[i] = obj.get_koord( str_smoketile, koord(0,0) );
  ```

  No regex over `obj.get("…")` sees that key, and no regex over `sprintf` sees it
  either. So the linter reported all thirteen of pak128's smoking factories as using
  a key makeobj ignores — and they are perfectly correct; makeobj reads
  `smoketile[0..3]`.

  makeobj settled it, not an argument: compiling `bakery.dat` as it ships and again
  with the index removed puts *the same* `(0,2)` and `(4,-57)` in the `.pak`, and
  flips one byte — `num_smoke_offsets`. The literal in the initialiser is the only
  trace such a key leaves, so that is what the extractor now harvests.
