Notes › Resources › Two Public-Domain Classics Merged into 555733 Bytes: the Panel Reads 2334 Lines and 187299 Characters, and Each Separator Tier Adds Its Own Slots

Two Public-Domain Classics Merged into 555733 Bytes: the Panel Reads 2334 Lines and 187299 Characters, and Each Separator Tier Adds Its Own Slots

Two whole public-domain classics, 445453 and 110280 bytes, merge into one 555733-byte file: the panel reports 2334 lines and 187299 characters, always 1 above the disk line count, with the four separator tiers and the file-name marker measured on the real books.

Two whole books must become one file before the next step means anything

When classic TXT moves further down the pipeline, the first step is usually concatenation rather than algorithm: one book per file means every split and search runs twice. This round goes straight for whole books. The template txt-merge.html measured 10126 bytes over 234 lines as it stands; its script section, 6844 bytes across 169 lines, went into node:vm exactly as stored, with no source line rewritten, and the DOM was stubbed around the 9 ids the page reads. The run was pure Node, with no browser opened.

Both samples are public-domain classics: the Guwen Guanzhi copy is 445453 bytes, holds 2089 LF, splits into 2090 segments with string length 150341; the Six Records of a Floating Life copy is 110280 bytes, with 244 LF, 245 segments and string length 36958. Both went into the text merge page in one drop, the dropdown left on its default tier.

Panel, disk, per-book segmentation: three rulers, three readings

The statistics bar reports file count 2, total lines 2334 and total characters 187299; the same artifact on disk is 555733 bytes, and a plain line count returns only 2333. Not a sum: the two books contribute 2335 segments and the merged panel sits 1 below that, while their LF adds to 2333 and the merged file sits 1 above. Across the nine tiers probed the panel count always exceeds the disk count by 1, because that field runs result.split('\n').length and the empty segment after the final newline still takes a slot. Bytes and characters are pure addition instead: the books summed alone and the merged artifact differ by 0 at both layers, and the byte-to-character ratio is 2.9671.

One artifact, four rulers, four readings Guwen segments 2090 · LF 2089 Fusheng segments 245 · LF 244 Segment sum of the two 2335 LF sum of the two 2333 Merged panel lines 2334 Merged disk lines 2333 The panel is always 1 above the disk count.

Figure 1: segments, LF, panel lines and disk lines crowd into the three cells from 2333 to 2335.

Four separator tiers: what each adds in lines, characters and bytes

None of the four options carries selected, so the browser lands on the first entry, no separator, and getSeparator (L168–L175) returns an empty string. Lines depend only on how many newlines the separator carries: panel lines = the LF sum + (n−1) × LF(separator) + 1, and ticking the name marker layers another n on top. Over the 13 tier combinations tested the prediction and the panel reading agreed character for character.

TierPanel total linesTotal charactersBytes on disk
No separator2334187299555733
Line break2335187300555734
Blank line2336187301555735
Custom three hyphens2334187302555736
Blank line plus marker2338187343555793

An empty custom box is not a fallback: that branch takes the empty string as it is, so the output matches the no-separator one byte for byte. A separator without a newline adds characters but no lines - three hyphens add 3 characters and 0 lines, and the seam becomes 12 code units wide; four equals add 4 characters and 0 lines.

The slot that is missing is not the trailing newline

Both books end on byte 0x0A with no carriage return, so the seam is character for character the first line of the Six Records file, 9 code units standing alone. Cutting the last byte off each book produced A2 (445452 bytes, LF 2088, 2089 segments) and B2 (110279 bytes, LF 243, 244 segments); the four pairings read 2334 / 2333 / 2333 / 2332 on the no-separator tier, each 1 below its own two-book segment sum, 0 on the line-break tier and +1 on the blank-line tier. The last byte changes the seam's content instead: with the book that lost its newline first, the seam fuses into 55 code units, that is the previous last line's 46 welded onto 9 more, and three hyphens in front push it to 58.

The file-name marker: every block is 13 plus the name length

The marker string '===== ' + name + ' =====\n' (L186) carries 1 newline of its own, so each block costs 13 code units plus the name length; the checkbox ships without checked, so it is off by default. Four naming schemes went through the same merge:

  • Book names in Chinese, 8 code units each: 21 per block, the two blocks adding 2 lines, 42 characters and 58 bytes, since each Han character costs 3 bytes
  • Pinyin names of 17 code units each: 60 across the two blocks
  • 8 code units paired with 34: 68
  • Names with a directory prefix, 20 code units each: 66

The line increment stays at 2, while the byte increment differs by scheme.

Batches, repeated drops, and nothing updating live

A single book reads the same on all four tiers, because joining a one-element array never inserts a separator: 2090 lines, 150341 characters, 445453 bytes. Six parts, each book three times, bring the LF sum to 6999, so the no-separator merge reads 7000 / 561897 / 1667199 and the blank line plus the marker reads 7016 / 562033 / 1667383. On that tier the panel minus the sum of the separate files is always the negative of n−1. Re-dropping a file still enlists it, since push does not de-duplicate, and the list refreshes once per batch, so three clicks on merge merely overwrite the statistics bar three times.

Change the dropdown, fill the custom box, tick the checkbox: the result area does not move. Those controls carry 0 listeners; only another click on merge moves the readout to 2338 lines and 187343 characters. Add a file after a merge and the queue holds 3 while the panel still reports 2.

Encoding, and a few failure paths

The first 3 bytes of both books are e3 80 90, the encoding of 【, and neither carries a byte order mark. A strict decode passes in one go, a lenient one yields 0 replacement characters, so neither account can arise here. Clicking merge with an empty list raises only the toast 请先添加文件. A 0-byte file merged alone gives the contradictory reading of 1 file, 0 lines and 0 characters. A mixed batch with one success and one failure leaves a single row while the toast still claims 2 files added, since the number printed is the number submitted. In the script section the byte threshold, 1024, max= and the truncating slice all score 0 hits, and the whole 1667199-byte artifact goes to the result box in one assignment.

Structure the books themselves give up

In the Guwen Guanzhi file, 238 lines are bracket markers standing alone, and the two bracket code points each occur 238 times, cross-checking the total: 12 volume headers, 4 book-opening markers, 222 piece titles. The headers fall at lines 44, 248, 438, 590, 713, 806, 904, 1020, 1136, 1292, 1510 and 1832; cutting on those 12 lines and counting titles volume by volume gives the distribution below.

The 222 title lines split across the 12 volume headers 18 16 22 17 15 16 19 19 22 19 21 18 Vol. I Vol. II Vol. III Vol. IV Vol. V Vol. VI Vol. VII Vol. VIII Vol. IX Vol. X Vol. XI Vol. XII The total is 222, matching 238 minus 12 minus 4.

Figure 2: the twelve volumes bar by bar, the third and ninth sharing the two tallest bars at 22.

The Six Records file carries only 4 marker lines, at 1, 54, 91 and 148, each 9 code units, marking the four parts inside it. Two pages of the same family share a ruler: the character field there and the character cell here both follow the JavaScript string-length definition.

Against the list copy, 1 of the 13 judgements stands inverted

Judging the txt-merge entry of config/tools.php from line 1608 onward gives 7 agreements, 5 partial agreements and 1 conflict. Three of the partial ones concern accounting: the title names only TXT while the accept attribute admits 11 types; the separator sentence omits the default tier and the missing fallback of an empty custom box; the batch wording reaches only to front-end concatenation. The conflict is precisely the live preview claim. Marked not measured, with reason: what the browser's own decoder returns from the literal bytes, what a file name measures under a disk encoding, how long writing 187299 code units into the box takes, and how tall that box renders.

Frequently asked questions

Q: Why is the panel line count always 1 above the disk count?

Because that cell counts segments after splitting on the newline, and the empty segment after the final newline claims a slot too.

Q: If the separator is custom but the box is empty, does it fall back to no separator?

The result matches, but it is not a fallback: that branch reads the empty string as it is, with nothing behind it, so the output is byte for byte the no-separator one - 555733 bytes, 2334 lines, 187299 characters, all equal.

Q: Does the result area change when the tier or the marker changes?

It does not. Those controls carry no listeners, so after a change you must click merge again to move from 2334 lines to 2338 lines and 187343 characters.

📎 Related articles

Two Whole Public-Domain Classics Fed into the Text Reader: Line Counts at 81474 and 78497 Bytes, the Code Unit and Code Point Gap, and the GBK Misread Replacement-Character Bill 2026-11-12 Three Hash Implementations on One Page: Two Public-Domain TXTs as Byte Corpus, Measuring the Block Ledger and Encoding Mutations of file-md5 Character by Character 2026-11-09 Shenglü Qimen and Liweng Duiyun Fed Whole Into the Text Diff Page: 154 Common Lines, 665 Added, 401 Removed, and the Stats Line Gives Only Two Numbers 2026-11-08 Measuring a Pinyin Table With Liweng Duiyun: 7160 Hits, 21 Off-Table Chars, 232 Polyphones 2026-11-07 Does the Thousand Character Classic Hold 1000 Distinct Characters: Word Count Measures 996 in Range, 1000 Real Han, 995 Distinct 2026-11-05