How to fix a bitmap font showing the wrong characters
You load the font, draw SCORE, and get TDPSF. The file is valid, the engine is fine, and every glyph is drawn perfectly — just not the one you asked for. Here is why, and the four other ways a generated bitmap font goes wrong.
1. Check that it is a shift
Take the wrong output and the right input and compare them position by position. If every character you got sits the same distance along your character set from the one you wanted — S gave you T, C gave you D, all one to the right — the mapping is shifted.
That is worth establishing first, because a shift has one cause and one fix, while a font where only some characters are wrong has a different problem entirely — a glyph that was split in two, usually, which shifts everything after it but nothing before.
So the sharper test is: render your whole character set, in order, as a single string. Everything up to the first wrong glyph is correct. That is where to look.
2. The five things that shift it
A generated font pairs boxes with characters in order: the first box gets the first character. Anything that changes the number of boxes before a point shifts everything after it.
A stray pixel. One opaque pixel in the margin, left from an eraser that did not quite finish, is an island like any other and counts as a glyph. This is the most common cause by a distance, and the most invisible: at 1:1 on a dark background, a single pixel is not something you notice.
A blank cell. If the sheet is laid out as a grid and one cell is empty — a gap where a character has not been drawn yet, or the cell reserved for the space — automatic detection finds nothing there, because there is nothing to find. Your character string still has a character for it. Everything after shifts the other way.
A glyph made of two islands. The dot of an i, the two dots of a :, the bar of an !, both halves of an =. Counted separately, each of these adds a box. A tool that joins islands by proximity has the opposite problem — it will happily join two neighbouring letters — so the rule has to be joining by horizontal containment: a dot sits over its own stem and not over the letter beside it.
Whitespace in the character string. A trailing space, or a newline pasted in from somewhere else, is a character like any other and takes a box. It is invisible in the field it was typed into.
A sheet that is not in reading order. Detection sorts boxes left to right, top to bottom, grouping rows by vertical overlap. A sheet whose rows are staggered, or where one row's tall glyph overlaps the next row's band, can be sorted into an order that is not the one you drew it in. Rare, but it looks exactly like a shift.
3. Finding where the drift starts
Render the character set as text and read along it until the first character that is wrong. Say your set is ABCDEFGHIJ… and the output reads correctly through G and then goes wrong. The mismatch is at the eighth box, so the extra or missing one is at or just before H on the sheet.
Then look at the sheet at that point, with the detected boxes drawn over it. One of three things will be visible: a box around nothing, two boxes around one character, or a character with no box. Each maps to one of the causes above.
For long character sets this is faster than it sounds — you are looking for the first mismatch, not comparing the whole string, and the first mismatch is usually in the first row.
4. The other four failures
Everything is blurry. Not a font problem. The texture filter is interpolating between pixels: set it to nearest — Point in Unity, Nearest in Godot and LibGDX, pixelArt: true in Phaser. If the text is only blurry at certain positions, the font is being drawn at fractional coordinates; round the position to whole pixels before drawing.
Letters overlap or sit too far apart. That is the advance width — how far the pen moves after each glyph. A font generated from tightly trimmed boxes needs a pixel or two of tracking added; one generated from grid cells that were not trimmed has the opposite problem, every letter a full cell wide. Set the tracking against real text rather than against the alphabet, since the alphabet has no short words in it.
Spaces have disappeared. A space has no pixels, so nothing can detect it and it will never be one of the boxes. It needs a width set explicitly. If your character string includes a space, that space is also taking a box it should not have — which is cause number four above, and one more reason the two symptoms often appear together.
Letters jump up and down on the line. Each glyph's vertical offset is measured from the top of its own row, so glyphs of different heights sit where they were drawn. If a row's tallest glyph is missing — the row has no A or l in it — that row's top is measured from something shorter and the whole row rides high. Draw the sheet so each row has at least one full-height glyph, or set the offsets by hand.
5. Before you blame the font
Two engine-side things look like font bugs and are not.
The .fnt refers to its image by file name. If the two are in different folders, or the image was renamed after the font was generated, the loader either fails outright or — in a few engines — silently draws nothing.
And a .fnt is not a .ttf. Engines have separate loaders for bitmap fonts: load.bitmapFont rather than a CSS font face in Phaser, BitmapFont rather than FreeTypeFontGenerator in LibGDX. Passing a .fnt to the wrong one produces errors that have nothing to do with your characters.
Common problems
Every character is one along from the right one
An extra box before the first wrong character — a stray pixel, or a glyph counted as two islands.
Every character is one behind
A missing box: a blank cell that could not be detected, or a character in your string that has no glyph drawn for it.
The first half is right and the second half is wrong
The shift starts partway through. Find the first wrong character and look at the sheet there.
Only i, j, ! and : are wrong
Those are the glyphs made of two islands. They are being counted twice.
Words run together with no gaps
The space has no width set. It cannot be detected — it has no pixels.
The font works in one engine and not another
Check that the .fnt sits beside the image it names, and that you are using the bitmap font loader rather than the TrueType one.