Skip to content

feat(v2): KIX, the second four-state postal code - #232

Merged
s2x merged 1 commit into
v2from
v2-kix
Sep 3, 2026
Merged

feat(v2): KIX, the second four-state postal code#232
s2x merged 1 commit into
v2from
v2-kix

Conversation

@s2x

@s2x s2x commented Sep 3, 2026

Copy link
Copy Markdown
Member

KIX — the Dutch four-state code, kix, aliases kix-code, klantindex, postnl. Pure PHP, no options.

What it is

RM4SCC's alphabet with the envelope taken off. The whole symbol:

C1 C1 C1 C1  C2 C2 C2 C2  ...  Cn Cn Cn Cn

No start bar, no stop bar, no check character. So KixTest asserts the symbology in one line — a payload's bars are its characters' bars concatenated — because every mistake available here is a mistake of addition.

The shared alphabet

The thirty-six characters are identical to RM4SCC's. That was measured, not assumed: the two fixtures were drawn independently through zint and agree character for character. So the derivation moved into Generator\FourState\Alphabet and Rm4scc\Characters now holds only what is RM4SCC's — start bar, stop bar, check character. Two copies of a base-six derivation are two chances for one to drift, and a drifted copy still draws thirty-six legal characters; a test asserts they still agree.

What KIX does not do

Worth stating, because a postal code sounds like something that checks itself:

  • No error detection over the symbol. A dropped or added character produces another legal KIX symbol saying something else. What survives is the per-character two-of-four parity, which catches a misread bar, not a misread symbol. Sorting machines read KIX beside an address they can compare it against.
  • No start or stop pattern. The quiet zone is load-bearing here in a way it is not in RM4SCC — with nothing to recognise, white space is the only thing marking where the first character begins — so it stays on all four sides.

Eighteen characters is the ceiling, and this time it is the specification's rather than the reference encoder's.

Verification

Same arrangement as RM4SCC, same caveat: no free decoder reads a four-state postal code, so this is one independent opinion where the rest of the library has two.

  • tools/kix_reference.pytests/fixtures/kix_reference.csv, 188 symbols, 36/36 characters, lengths 1 to 18. Every character is drawn alone, at the front, at the back and mid-symbol, since an encoder that had grown an RM4SCC-shaped start bar would still get every one-character symbol right. The tool asserts zint's own output is a concatenation before writing anything.
  • The substituted round trip — the rendered PNG measured back into state letters and compared with zint's — is now one test over the family rather than one per symbology. The next four-state code adds a row to fourStateProvider() instead of deciding for itself whether it wanted the gate. Same for the expiry test that fails the day zxing-cpp learns to read one.

The encoder matched zint on 2500GG30250 on the first run and on all 188 fixture rows.

Checks

composer lint clean; full suite green (15390 tests, 264323 assertions, the 21 pre-existing PHPUnit deprecations); SCANME_REQUIRE_DECODER=1 composer test:roundtrip green (356 tests, 1389 assertions).

The second four-state postal code, and the smallest symbology in the
library: no start bar, no stop bar, no check character. A KIX symbol is
its characters concatenated and nothing else, which is asserted directly
because every mistake available here is a mistake of addition.

The thirty-six characters turned out to be identical to RM4SCC's --
measured, by drawing both fixtures independently through zint -- so they
moved into Generator\FourState\Alphabet and RM4SCC now keeps only what is
its own. Two copies of a base-six derivation are two chances for one of
them to drift, and a drifted copy still draws thirty-six legal characters.

What KIX does not do is written down rather than left to be discovered:
there is no error detection over the symbol, so a dropped character
produces another legal symbol saying something else, and the quiet zone is
load-bearing because nothing else marks where the first character starts.
Eighteen characters is the specification's ceiling, not zint's.

The substituted round trip is now one test over the family rather than one
per symbology, so the next four-state code adds a fixture row instead of
deciding for itself whether it wanted the gate.
@s2x
s2x merged commit 0b52e47 into v2 Sep 3, 2026
5 checks passed
@s2x
s2x deleted the v2-kix branch September 3, 2026 19:50
s2x added a commit that referenced this pull request Sep 3, 2026
feat(v2): KIX, the second four-state postal code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant