Odin and Me ยท Appendix D

Appendix D: Troubleshooting

The traps that recur across our code, gathered in one place. If something's behaving strangely, check here before assuming you've found a new problem.

An off-by-one in a nested loop

Every shape in Chapter 5 depends on getting 1 ..= height versus 0 ..< height, and a padding formula like height - row, exactly right. If a triangle or diamond comes out one row short, one row too many, or lopsided, check the loop bounds first: ..< excludes its top end, ..= includes it, and mixing them up by habit is the single most common mistake in that chapter.

Counting bytes instead of runes

len(s) is a byte count, not a character count (Chapter 4). Code that assumes the two match works fine on plain ASCII test data and then miscounts, or slices into the middle of a character, the moment real text has an accent, an emoji, or anything outside ASCII in it. If a string operation is producing a suspicious replacement character (\uFFFD) or a count that looks one or two off from what you'd expect by eye, check whether you meant utf8.rune_count_in_string instead of len.

A stale scratch-buffer string

This one is inherited house style from the wider dinn project, and while our core:os/ core:bufio calls don't share Odin-WASM's JS-boundary scratch buffer specifically, the general shape of the bug is worth knowing: a string handed back by one call can become invalid the moment you make another call that reuses the same underlying buffer, before you're done using the first string. If a value looks corrupted only sometimes, and only after a second unrelated call happened in between, suspect this shape of bug rather than something wrong with the value itself.

A terminal left in a stuck colour or mode

If your terminal prompt is stuck printing in a colour after a Muninn crash or an interrupted run, that's Chapter 7's Gotcha in the wild: set_colour was called without a matching reset_colour on every exit path. Type reset (or tput sgr0) in your shell to fix it immediately; if you're writing your own program, make sure every branch that can end the program, including an error path, resets colour before it does.

The Windows legacy-console ANSI gotcha

If ANSI escapes show up as literal [2J/[31m text on a Windows machine instead of being interpreted, that's an older cmd.exe that needs enable_ansi()'s SetConsoleMode call (Chapter 7 ยง7.7) to run first: confirm it's actually being called at the very top of main, and that you're not on a console old enough to need a Windows update instead.

A corrupt save file

Chapter 11's load_notes reports how many lines it had to skip (warning: skipped N unreadable line(s)) rather than crashing or silently losing every note. If you see that message and it surprises you, the file has probably been hand-edited, or was written by an earlier, less careful version of the save code: open muninn_notes.txt in a text editor and look for a line that doesn't have exactly three \x1f (unit separator) characters in it; that's the shape decode_note rejects.

A validated-input loop that never exits

If a for { ... } retry loop like read_int_in_range (Chapter 11) seems to hang, check the exit condition first: it's easy to write the comparison backwards (n > hi || n < lo swapped, or a range's lo/hi passed in the wrong order, see Chapter 11's exercise 3) so that no input ever satisfies it, and the loop re-prompts forever no matter what's typed.