The current state of the reverse engineering: what reads, what is measured, what the corpus holds. A snapshot, dated by its commit, and it is the document README.md points a reader at who wants more than the front page.
This is not the plan. ../todo.md is the plan of record: the decisions, the
milestones and the sequence, and what gets answered next and why that one. The line between them is
worth stating because this project has already been bitten by the other arrangement, on 8 August 2026,
when an audit found eleven places where the documents contradicted the code and every one of them was
in a summary rather than in findings.md. So: the roadmap says what is intended, this
page says what is true today, and neither restates the other. Where a number appears here it carries a
fact: marker and make facts recomputes it from the corpus, which is what stops this page becoming
the twelfth.
For the argument behind any claim here, findings.md is authoritative and numbered by section. For the format as a specification, config-format.md.
The work targets three architectures. Nine remotes are on the bench, and this list is the
hardware, not the model families: two Harmony Ones, a Harmony 600, a Harmony 525, since 27 August
2026 a Harmony Touch, a Harmony 350 and a Harmony 300, since 27 September 2026 a Harmony 650, and since
29 September 2026 a Harmony 700. This said seven until 9 October 2026, two units short, the same count
CLAUDE.md had corrected for itself. The last three are the file based family and
are not targets for the config work: openHarmony refuses them by product id
rather than by accident, section 193. What they have bought so far is the reading of the descriptor
field that names a model, section 195, the route to their firmware, section 196, and their whole
protocol as Logitech specifies it, section 198. There is an implementation now and it has read a
remote, sections 200 and 201: packages/usb/src/filepipe.ts, and a Harmony Touch's /sys/sysinfo
opened, read and closed on the bench, 234 bytes in fourteen fields including the architecture the
remote states for itself. This paragraph called it a specification and not an implementation until
29 August 2026. Reading that identity is four commands, ping, open, read and close, and none of them
writes, so openHarmony's refusal is about what has been built for the config path rather than
about what is reachable at all. Other models
appear throughout this page as firmware images or contributed configurations, and the Harmony 700 is
the one that gets mistaken for hardware, because it is the best mapped arch 14 image and is quoted
constantly. A Harmony 700 unit has been on the bench since 29 September 2026, and this said there
had never been one until 9 October 2026.
- arch 12 ("Gin"), the Harmony One, and the spare Harmony One, one of the three units anything may be written to. This said it was "the only unit anything may ever be written to" until 5 September 2026
- arch 14, the Harmony 600, since 27 September 2026 a Harmony 650, and since 29 September 2026 a Harmony 700, which was a reference image before that: two configurations and a firmware image, no remote. The 650 is the third unit that may be written to and arch 14 the third architecture written to: one block of its own bytes put back unchanged the day it was permitted, section 281. The 600 and the 700 may be written to since 29 September 2026, Danny's decision, and both have had a configuration changed since, sections 301 and 303; this said "neither has been yet" until 9 October 2026. The three share a product id and an architecture, so the dump names which unit is expected and the unit check against the identity block is what refuses any other
- arch 9, the Harmony 525, connected on 8 August 2026 and a target since: its config and its firmware are in the lab, and its class 5 infrared, which was the last big gap in the byte accounting, is read. It is the second unit that may be written to, Danny's decision of 5 September 2026, and the second architecture this project has written to: one block of its own bytes put back unchanged on 6 September, section 269. This said it "has no write target and will not get one".
Firmware is held for four architectures, 8, 9, 12 and 14, plus a fifth, architecture 16: Logitech's own software update service serves the Harmony 300 and Harmony 350 image to an anonymous request, section 196. It is the first firmware here to come from the vendor rather than from hardware, a contributor or a repair site, and it is an ordinary PIC18 image at the same load address as arch 14's.
This said the architecture was not established and that no config of it had been read, until
29 August 2026, twenty lines above the paragraph in this same document that says a Harmony 350
brought architecture 16 with it. Both halves were already false when written: section 194 read a
Harmony 350's configuration through concordance and it passed all fifteen framing checks, with slot 1
stating architecture 16 and the remote itself reporting the same over USB, which is two routes with
nothing in common. The load address argument was sound and it was answering a question that had been
closed by other means. What remains true is that openHarmony cannot open one: the file based
protocol reaches its storage by name, and the one read path that goes through it is
openFileBasedRemote, which reads a configuration as a file and writes nothing, section 262. This
said no read path here went through it until 9 October 2026, which section 262 had overtaken.
Established: the MCU family, firmware load addresses, flash layouts, the firmware image header and its checksum, the config container, the keypad scanner, and the complete infrared path from config pointer to LED including the SPI storage layer.
The container is now validated across four architectures, because publicly shared sample sets (arch 8, arch 9 and a Harmony 700 pair) were added as controls. Seventeen samples in the framing tables, five base addresses and three pointer table lengths, which is the same property the format word states rather than a second one, section 194, so it is counted once. All consistency checks pass; the wider population of everything in the lab that parses is 48 containers over six architectures, since arch 10's framing verifies too and a Harmony 350 arrived on 27 August 2026 bringing arch 16 with it, section 194. It turns out to be one format with a per architecture cookie rather than one format per architecture, and the pointer table is one table too, with a couple of per architecture insertions, so a section labelled on one architecture transfers to the others by index.
The trailer checksum is derived: a sixteen bit XOR of the container's little endian words
seeded 0x4321, recomputing on every container in the corpus. That was the last thing standing between
a generated config and a remote that would accept it.
Every one of the twenty base slots is accounted for, and every slot from 2 to 19 has a located
firmware consumer: two header records, sixteen named sections, and two that are NULL in every
sample. Among the recent ones is the timer table, which is where a backlight timeout and a two hour
power off live: a record says how long to wait and which single instruction to run afterwards, and
the set of records a config's action lists start is exactly the set it declares. Next to it is
the parameter block, whose every group has a length the firmware demands and silently ignores
the group if it differs: fifteen such lengths read off two images, seven for arch 14 and eight for
arch 12, holding in every container of
the two architectures those images belong to, and asserted on no other. Then the touch screen hit map, which only the Harmony One
carries, because it is the only remote here with a touch panel: per screen page a list of
rectangles, each reporting a key code, and the firmware answers a touch with the first rectangle
the point falls in. The last to fall is the log area, which is not a pointer to anything: three
numbers reserving a region of flash above the config that the firmware appends to and never erases,
its write position recovered at boot by scanning for the last byte that is not 0xFF. A config
states its own architecture in section slot 1, which is what lets a config read over USB be
parsed without the file header Logitech's software supplied. Slot 0 is the container's only
0xFEED/0xBEEF frame, and slot 3 is a second framed record holding the date and time the
config was built, decoded by a search that only one field assignment survives and confirmed by a
weekday byte that is the date's weekday counted from Sunday, the day itself being counted from 0,
docs/findings.md section 322, which found every date this project had reported a day early. Six more are count prefixed arrays of three byte flash pointers, proved
to be pointers by a controlled pair of configs from one remote in which every entry moved by
exactly the layout shift. One of those six is the action list table: on the Harmony 700 it
addresses 8037 lists holding 19651 instructions, and all but four consecutive entries sit exactly
1 + 3 * count apart, which is the pointer table and the lists' own count fields agreeing.
The key table is decoded: an event code is an event type plus the keypad scanner's scan code, not the matrix address it was read as here for a while. That correction turned the Harmony 600's table from something that provably could not describe its own keypad into 54 keys times three event types, exactly.
That has now been checked against the remote, by pressing all 54 of its buttons while the host
watched the keypad port over USB. A remote on USB never runs its keypad handler, so the scan code
is never computed and only the matrix column is observable, a quarter of the mapping. (It does run
the rest of its application, section 111. The broader claim stood here for three days.) That quarter
closes: the measured census is 14, 14, 13, 13 buttons per column, a column holds at most 14, and
the unit's own config carries scan codes contiguous 1 to 54, whose two absentees fall in exactly
the two columns that are short. Which button carries which of the 54 codes is named for 36 of them, through the account that
compiled the calibration configs, section 133 and reference/button-maps.md; the rest, and every
contributed config, stay open. The
Harmony One gives nothing at all: sixteen buttons from every region of it pull one shared sense
line, so arch 12 wakes differently from arch 14 and USB yields no part of its mapping.
Both of the config's languages are read, and with them the text: base slot 7 is the font table,
run length encoded glyphs at two bytes a pixel, or two bits on the monochrome 5xx panel, and
every one of 67303 inline string codes in the corpus resolves to a glyph of the font its own
program selected. tools/screen_dump.py --strings draws them, and they come out as readable
labels. Action lists are bytecode for an accumulator machine with a forty instruction queue and
a binary search dispatcher, and the queue is in RAM because the language mutates it: a comparison can
carry an else arm, and it cancels the arm it does not take by writing a do nothing instruction
over it, section 140. And a second interpreter draws the screen: its own one byte opcodes
for text, bitmaps, a switch on a state variable and a jump, with 22846 programs across
19 containers decoding with nothing left over. Its one instruction that
names an address outside its own program draws a bitmap, either raw rows or the same encoding a
glyph uses, and the firmware states two rails a writer needs: only the low byte of each size field
is loaded, and the row loop stops drawing above row 128 while still consuming the stream.
That region is read now, and with it most of a config. It used to be the largest single unknown, 62% of a Harmony 600 and 82% of a Harmony One reachable from nothing named. It is one contiguous array of screen pictures, rows of big endian RGB565 pixels on three architectures and one bit a pixel on the Harmony 525's monochrome panel, drawn by programs carried inside mode records that nothing could reach until a missing operand count was found in the firmware. A mode turned out to have pages, each naming its own key map and its own screen, and following those took the Harmony One from 28 pictures reached to 98, which is every picture in its bank. The byte accounting is the measure of it: the fraction of a config attributed to a structure the codec understands, with any two structures claiming the same byte reported as the defect it is.
The table below replaced a five row summary of itself on 6 September 2026. That summary lived here
while the full history lived in the roadmap that was retired that day, and it was a strict subset:
the same measurement
with two samples missing and every intermediate column dropped. Two copies of one measurement in two
documents is what the retirement of that document was for, so the full one moved here and the summary
went. tests/test_documents.py holds this table's shape.
Measuring it first changed what it is. The obvious reading of M2 is "write an emitter", and it
is wrong: an emitter can only rebuild what a reader can attribute, so the first question is what
fraction of a config is attributed at all. packages/codec/src/coverage.ts answers it and
make coverage prints it. Where it started on 7 August 2026, and where the first two ports took
it the same day:
| sample | at the start | readers ported | mode records, 53 | opcode 23, 54 | the bank, 55 | infrared, 61 | arch 9, 63 to 65 | pages, 66 | slot 9, 67 | the pool, 67 | header groups, 75 | class 5, 82 | the residue, 83 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Harmony 700 | 11.4% | 26.3% | 59.3% | 87.8% | 91.9% | 98.1% | 98.1% | 99.5% | 99.6% | 100.0% | 100.0% | 100.0% | 100.0% |
| Harmony 600 | 9.5% | 24.8% | 57.5% | 86.4% | 87.4% | 98.7% | 98.7% | 99.6% | 99.7% | 100.0% | 100.0% | 100.0% | 100.0% |
| Harmony One | 3.2% | 8.0% | 8.6% | 47.9% | 90.0% | 98.0% | 98.0% | 99.6% | 99.8% | 100.0% | 100.0% | 100.0% | 100.0% |
| Harmony One, spare | 3.2% | 7.5% | 7.9% | 54.5% | 97.0% | 98.6% | 98.6% | 99.8% | 99.8% | 100.0% | 100.0% | 100.0% | 100.0% |
| 880, arch 8 | 3.6% | 16.4% | 50.6% | 80.2% | 82.2% | 94.4% | 94.4% | 97.0% | 97.2% | 97.7% | 100.0% | 100.0% | 100.0% |
| Harmony 525, arch 9 | 7.2% | 10.4% | 14.1% | 14.1% | 14.1% | 14.6% | 55.1% | 64.1% | 65.1% | 66.4% | 67.1% | 99.9% | 100.0% |
| the three safe mode containers | 4.2% | 70.2% | 89.5% | 89.5% | 89.5% | 91.8% | 91.8% | 98.2% | 98.4% | 99.4% | 99.4% | 99.4% | 100.0% |
Only the last column carries a fact: marker, and that is the rule rather than an accident: a
historical column is a fixed number and the live one is recomputed from the corpus. Putting a marker
on a history column makes make facts-write rewrite the past, which is exactly what happened to
this table for one commit.
The sixth column is two readers landing the same day: base slot 13's records, found by asking the
deliberately built config pair of section 58 one question, and the infrared records, whose header
points backwards at duration blocks below it. Its length was read as a flat 21 bytes and is
12 + 9 * count, section 75, which is the last column.
The header groups column is one byte. An infrared header states how many nine byte pointer groups it carries, and 37 records a config on arch 8 carry two. That closed arch 8 outright, from 97.7% to 100.0%, and moved arch 9 as well, and it needed no firmware: the reading came from the corpus, out of three gap families whose counts were all 37. Section 75.
The class 5 column is arch 9's infrared, and unlike the one before it, it needed the firmware. Class 5 spells a code as indices into a shared table of pulse blocks, section 82, so 25776 of the 25819 bytes the 525 had left were three structures nobody could size without the code that reads them. It closed the last architecture sized hole in the accounting.
The last column is the residue, sections 83 and 84, and it is where one decimal place stops being enough. Section 83 read three shapes, that base slot 0's frame is two bytes longer than the length it states, that an empty counted array is still an array, and that the bytes above base slot 7's table are base slot 8's leading action list, which took every user config to 100.0% with 4 to 68 bytes left in each. Section 84 read those: a screen program carries a terminator even where a jump means nothing reaches it, base slot 3's section is three bytes longer than the clock record, base slot 17's is two where it names the picture bank, the key table's extent is its mode record's, and twelve arch 12 bytes belong to base slot 15 and to no group. The column reads the same either way and the difference is the whole milestone, so it is stated in bytes rather than in percent: no container in this table has an unaccounted byte left.
The seventh column is where "arch 9 barely moves" stopped being true, which this table asserted for a day. Three findings on 7 and 8 August: its glyphs are two bits a pixel rather than two bytes, one missing operand count was hiding every one of its mode programs, and its infrared records share class 1's header. Nothing else moved in that column, and the reading at the time was that the other architectures were at the ceiling. They were not, which the eighth column says: base slot 6's entry had a page count and an array of pages nobody had read, and following them moved every architecture at once. What looked like a ceiling was one unread field.
Neither the fourth nor the fifth column is a reader. Section 53 is one rule, that a mode record
carries its own screen program, and section 54 is two corrections: opcode 23 takes no operand, which
is what was holding arch 12 shut, and a picture's stride is in pixels rather than bytes, which had
halved every raw extent. Together they take the region from an unknown to 98% pictures on a
Harmony 600, 93% on a 700 and 97% on arch 8, with the Harmony One at 48% and left as the open
item. Section 66 closed that one: every picture in an arch 12 bank is drawn by a program that can be
reached, 98 of 98 and 70 of 70.
Lower than the sixteen named sections suggest, and the reason is the shape of the file rather than a gap in the analysis. Most of a config is a pooled data region that the sections index into, and the readers return values without returning the bytes they consumed. Every screen program in the corpus decodes with nothing left over, section 40, and not one of them can yet say which bytes it occupied.
Zero overlapping claims anywhere. make coverage prints it. Every user config is accounted for
to the byte, not to a rounded percentage: a 1.63 MB Harmony One config has nothing unattributed
in it. That is the point at which an emitter can rebuild a config rather than copy a residue, and
packages/codec/src/emit.ts does: every owner the accounting claims is rebuilt on all nineteen
containers, and the residue copy writes nothing at all on eighteen of them.
Arch 9 joined the rest once its own firmware was read: class 5 infrared is a dictionary encoding,
section 82.
A config also says what it is for, which is what an interface has to show before it can let
anybody edit anything: a device is an infrared group, and one state variable, named
CurrentActivityState in every container that carries names, counts the activities. The
calibration is a config Logitech's own service compiled for one device and one activity while we
watched, which reports one and one. The names in that tree are our own equipment, so what
is published here is counts and shapes rather than anybody's inventory. Section 86.
A config can be changed now, length and all. Same length edits came first, then a relocation
pass that inserts bytes and restamps every stated address, section 172, and on top of both a
composer: pick an appliance out of Logitech's catalogue and composeDevice plus
composeDeviceScreen put it on a real Harmony One config, infrared, state variables, key bindings
and its own screens, with the byte accounting, the round trip and the renderer all closing over the
result, section 173. The check that this is not merely self consistent is section 174: Logitech's
own compiler, asked to add the same television, writes infrared blocks byte identical to ours
once two of its spelling conventions are adopted, both measured rather than guessed. Nothing has
been written to a remote; the write gate opened on 25 August 2026 and the packet protocol for a
write was derived the same day, section 175, on both bench architectures.
Not established: three of the four infrared encoding classes, which no config in the corpus uses; which physical button each scan code is beyond the 36 and 32 the calibration account names, and every button's position, which is a wiring decision no read path reaches; and, on the write path, whether the firmware erases before it programs, which is moot for a caller that erases first, and whether the USB peripheral can accept a report before the firmware has serviced the previous one, which is a buffer descriptor nobody here has read. The firmware's own answer on pacing is that it asks for none. What keeps a write from happening is the rails and the unopened door, not an open question. See findings.md for detail and config-format.md for the spec as it firms up.
Everything here is derived from two remotes on a bench and a handful of files. Logitech shipped rather more than that.
| count | |
|---|---|
| models listed on the harmony-remote-forum comparison page | 42 |
| named models in concordance's skin table | 71, in 120 table positions |
| architectures concordance knows models for | 11 (arch 2, 3, 7, 8, 9, 10, 12, 14, 15, 16, 17) |
| architectures with hardware on this bench | 3 (arch 9, arch 12 and arch 14) |
| architectures with sample files only | 2 (arch 8 and arch 10) |
| architectures with firmware in the lab | 4 (arch 8, 9, 12 and 14) |
So the container claims are validated across five architectures and the USB claims across
three, out of at least eleven. The fifth arrived on 10 August 2026 with the kkong42 contribution,
which also brought the first arch 8 firmware: sections 113 to 115. Arch 10 parses and nothing reads
it, and section 117 turned that from an absent derivation into a result: arch 10's 23 slots are not
the base twenty with three inserted, because five readers are satisfied by none of the 1330 possible
placements. Every codec reader stayed gated at that point. The mapping was adopted on 26 August 2026, on
Danny's call, and this paragraph told a reader not to add one until 29 August: SLOT_MAPS in
packages/codec/src/gspm.ts is a table per architecture now, section 184, the standing figures are
fifteen base slots present and five absent, and the byte accounting reaches 99.3% and 97.2% on the two
890 configs. The live account is further down this document under decision 11's neighbourhood; what
is preserved here is why the mapping had to be stated rather than derived, which is that none of the
1330 possible placements satisfies five readers at once.
That section also corrected the container's base recovery, which had been circular since the first
day and was wrong on one of the two 890 configs. Arch 8 has firmware and is still a control, and what the
images bought was three counterexamples: the skin rule, GET_VERSION field 6's fourth value, and the
discovery that a firmware image can parse as a container, which had quietly admitted a program image
to a corpus wide percentage. The third arrived on 8 August 2026 and cost three changes to packages/usb,
every one of them an arch 12 assumption written as a universal: section 76. One boundary is already visible without owning anything: the 900, 1000 and 1100
are arch 15 and enumerate as a network class rather than plain HID, so the transport here cannot
reach them at all, never mind parse them.
There is a sharper version of the same point. Logitech's own discontinuation notice of 28 May 2025
names forty models whose accounts it closed, and not one of them is arch 12 or arch 14; they
are the older EasyZapper platform, spanning at least six architectures, while the bench holds two
remotes from the platform that came after. reference/models.md has the list and the mapping.
That gap is what step 8 exists for, and as of 12 August 2026 it is not what the project spends its time on: decision 10 below puts the application first and stops soliciting dumps. The gap is real, the table above stays honest about it, and closing it is not the next job.
Moved out of docs/plans/002-the-roadmap.md on 6 September 2026 when that document was retired. It is a
measurement of where the reading stands, which is this document's job, and tests/test_documents.py
holds the table's shape.
22 dumps from 9 contributors, carrying
43 configs across 5 architectures, plus the
firmware images and bootloaders the table lists per architecture. make corpus inventories it and, importantly, reports which
dumps nobody has described: a dump whose contributor has moved on is far harder to label later than one
described on arrival.
| arch | models | dumps | configs | firmware held | what would help most |
|---|---|---|---|---|---|
| 2 | 745 | 0 | 0 | none | anything at all |
| 3 | 748, 768 | 0 | 0 | none | anything at all |
| 7 | 610, 620, 628, 659, 670, 676, 680, 688 | 0 | 0 | none | anything at all: eight models and no sample |
| 8 | 880, 885, 880 Pro, 720, 785 | 6 | 16 | 880, 885 and 720, application and bootloader; the 880 Pro's is the 880's build with its own skin | a 785, which no sample here covers |
| 9 | 510, 515, 520, 525, 550, 555 | 3 | 3 | 525, 515 and 555, application and safe mode, three different builds | a 510, 520 or 550 |
| 10 | 890, 895, 890 Pro | 2 | 12 | none | firmware, which is the single hardest blocker here |
| 12 | One | 2 | 2 | One 3.4, plus safe mode and both internal pages | nothing: this one is covered |
| 14 | 600, 650, 665, 700 | 5 | 6 | 600, 650 and 700 | a 665 config |
| 15 | 900, 1000, 1000i, 1100, 1100i | 0 | 0 | none | out of reach by construction: a network class device, not HID, so this library cannot address one. One contributed 900 configuration sits in the lab, a file system of XML and settings files that make corpus does not count |
Arch 10 is the interesting gap and it is not for want of configs. The corpus holds reads of two Harmony 890s and one Harmony 895, their container framing verifies, and the twenty three pointer slots are not a relabelling of the twenty. That was inferred from reader scores, the best of 1330 placements reaching 34 of 47 where arch 8, 9 and 14 each score 47 uniquely, and it is proven since 26 August 2026: the Harmony 895's owner states its six devices, base slot 5's entry count equals the device count on 9 of 9 configs across the other four architectures, and no arch 10 slot holds a six entry array at all. Base slot 5 could only land on raw slot 5 to 8 under any placement, three of those are one, one and three bytes where a six entry array needs nineteen, and the fourth declares nine.
The mapping was then derived from content rather than fitted, and it is switched on, sections 183 and
184: fifteen base slots present, five absent and eight raw slots that are no base slot, with base slot
10's packing closure as the anchor that settled it. So a Harmony 890's screens, button bindings, action
lists, build timestamp and drawn text all read, the text at 5634 of 5634 glyphs, naming the same four
activities and four appliances as the arch 8 Harmony 880 from the same household. What it does not give
is the device names or the activity count, and that is structural: both routes need base slot 0's name
tree, which arch 10 has no slot for, or base slot 13's transitions, which section 184 refuted. The byte
accounting sits at 99.3% and 97.2% with zero overlaps, after section 185 read the biggest remaining
family as mode page screen programs: one table entry was missing, the operand width of the single screen
opcode that differs per architecture, and 49 and 34 programs were abandoned unread with a fifth of the
drawn text. That width was measured per program rather than off the coverage percentage, which prefers
a wrong answer that overruns and claims 308344 bytes twice while reporting a clean 100.00%.
What read there before any of that is what the framing locates, section 179: the picture
bank is found from the trailer's position alone, calibrated on 14 of 14 containers whose bank is known
by another route, so 57% of a Harmony 890's file and 72% of a Harmony 895's is accounted for as
pictures and both state a 128 by 160 display, the same as a Harmony 885. Its font sets read the
same way, section 180: eight of them on each, found by requiring every pointer to decode into a glyph
that tiles exactly, and their shapes are named by the arch 8 alphabet at 213 of 237 where the Harmony
One's alphabet names none. So a Harmony 890 uses the Harmony 885 typeface and its letters read, though
not its words, since a string's address comes out of a screen program and those need the mapping.
And its infrared database reads, section 181, because a record states its own address: 300 codes on
the Harmony 890 with all 463 duration blocks decoding, exact against the slot route on 13 of 13
containers elsewhere. The Harmony 895 has none, proven rather than unfound. And those three structures then identified the slots that name
them, section 182: an arch 10 config does state its architecture after all, 10, at raw slot 0, and it
has no name tree slot at all. The mapping is determined, section 183, and corrected and switched
on in section 184: fifteen base slots are present, five are absent, 0, 2, 8, 13 and 14, and eight raw
slots are no base slot. The decisive anchor is the action list table's packing closure, which exactly one
arch 10 slot satisfies with arch 8's own signature. The readers were gated deliberately at first: the
mapping lived as data in a test, because archSlot could not express an absent base slot and it is
a decision rather than a consequence. Firmware is what settles it, the way arch 9's own firmware settled its
infrared classes. Sections 115, 117 and 178.
Seven reads are not seven configs, and on this architecture that had to be measured, section 122. One remote was read twice and gave the same container twice. The other was read five times and gave five files that disagree with each other, not one of which recomputes its own checksum, because an arch 10 read duplicates whole 54 byte chunks: 16, 2, 6, 5 and 12 of them, with nothing ever lost. Removing the duplicates lands every one of the five on the same 396225 bytes, puts the end marker exactly where the header says, and reproduces the checksum the files declare, which is the closure. Three of those five arrived after the finding was written and carry 11, 13 and 17 chunks, so the prediction was tested on files nobody had seen. A contributed 890 config has to be checksum verified before it is believed, and a failure is a reason to read the remote again rather than to reason about the file.
Arch 8 is a control rather than a target. Eleven configs and two application images arrived on 10
August 2026 and what they bought was a counterexample supply: they broke the skin rule, they gave
GET_VERSION field 6 its fourth value, and they showed that "whatever parses as a container" is not a
corpus. Reach for them when a claim holds on every architecture, because a claim nothing can contradict
is this project's recorded failure mode.
concordance --dump-firmware returns no usable firmware on the two architectures here. This
is why the firmware had not been examined before. On arch 12 it returns a small config blob from
the wrong flash region. On arch 14 it returns real code, silently truncated to 64 KiB when the
image is larger. Both read flash_base = 0. It is an architecture table entry rather than the
tool, though, and on arch 8 and arch 9 the same command returns the whole firmware region, so
it stays the way to obtain an image for a model nobody here owns. See
../reference/concordance-notes.md.
It is a Microchip PIC18, and it disassembles cleanly once you have the right file at the right load address. 87% of the Harmony 700 image resolves into 521 functions.
| Image | Size | Execution base | Entry point |
|---|---|---|---|
| Harmony One 3.4 | 60050 | 0x020000 |
0x02EA38 |
| Harmony 600 0.2 | 70336 | 0x009000 |
0x01A26E |
| Harmony 700 2.8 | 76672 | 0x009000 |
0x01BB38 |
The two architectures store and execute firmware differently, which explains a lot of otherwise confusing detail. Arch 12 uses a parallel NOR flash mapped into program space and executes in place. Arch 14 uses an SPI serial flash, which is not executable, so the bootloader copies the image into internal flash. That conclusion was reached twice independently, once from branch target analysis and once from finding the SPI code.
The screen's text reads back, and a glyph code turns out not to be a character. It is an index
into the config's own font table, assigned per config in the order characters first appear in the
generator's string list, so two configs of the same remote disagree about it. What is stable is the
typeface, so a code is resolved by matching its glyph's pixels: seven hand read alphabets cover the
corpus and 170920 of 170922 drawn glyphs come back as
characters. The closure is that a decoded string turns up verbatim inside a base slot 0 name, which is
ASCII and which the decoder never reads. make text, and section 112.
Two thirds of that text is drawn by reference, and nothing read it for months. Screen opcode 4 draws the glyph string at an address, and in 12052 of 12052 instances across the corpus that address is the payload of an opcode 5 instruction in another program: a string is stored once inline and referenced everywhere else. The byte accounting never complained, because the bytes were already claimed by the program holding them. Section 121.
A config says which key starts which activity, sections 120 and 121, in four hops through a page's
key bindings, an action list, a base slot 9 set and a write to CurrentActivityState. The value that
means "no activity" is a field base slot 13 had carried unconfirmed since section 60, and one config
separates it from every rule that would have guessed it. Every activity in the corpus has its name,
50 of 50 on all four architectures, 22 of 22, 4
of 4, 11 of 11 and 13 of 13. make activities.
The Harmony One got there by a different route, and a better one, section 125. On a touch panel no fixed scan code to row map can exist, which section 121 proved, so a One's names cannot come from what its modes say. They come from the hit map instead: the arch 12 only spare byte in front of a mode page's pointers is the index into base slot 17, so the rectangle a key covers is stated by the container, and the label is the text the firmware's own hit test puts inside it. That also read the panel's geometry, which turns out to be three blocks down the screen, at most two side by side, plus a bar of two touch points below the display and a key at each side of it. Nothing was fitted to get there bar one offset.
Every device in the corpus has its name too, section 126, 63 of
63 across 15 user configs, and this one is ASCII rather than pixels. A
device's label is a prefix of one of its state variables' names, and what says which infrared group the
label belongs to is the variable's own transitions: they carry the action list that performs the change,
and for a power or input variable that list is the one that sends the code. 102 variables reach exactly
one group and none reaches two. The independent check is that the label is also drawn on the screen,
53 of 55 exactly, which is base slot 0's ASCII and base slot 7's glyph pixels agreeing through readers
that share no code. make devices.
And every key a screen labels carries that label, section 128, which is the last place this project could see a name and not say whose it was. Which keys a screen speaks for is stated: a scan bound by a mode page is one of them, a scan bound by a base slot 9 set is a key on the keypad, and the two populations are disjoint. Where the label is depends on the remote. A Harmony One states it in the hit map. Everything else puts its screen keys in two columns beside the display, and those rows are measured from where the activities of section 121, named without any geometry, are actually drawn: four rows on a Harmony 880, two on a 525, two on a 600 or 700. 98.9% of 6989 screen key bindings get a label, and 3100 of the 3106 that send an infrared code do. The rule that suggested itself, the k-th key taking the k-th row of text, fits the counts and gets two of four wrong on the 600's own activity menu, so it is recorded as refuted rather than quietly dropped.
And a config's screens can be drawn, section 129: make render writes the picture a page would put
on the display, which is the check no other test here can perform, since a reader that returns a number
cannot see a label half a row out of place. Every mode page of every config in the collection draws with
no picture and no glyph unresolved, on all four architectures. It also corrected something: a pixel is
stored high byte first, the only field in the format that is not little endian, and reading it the usual
way turned a Harmony One's buttons into rainbow stripes.
And the remote's clock is in the config twice, section 130, which fell out of drawing the screens: a screen switches on the state of the remote, asking which variable that is led to base slot 13's first field, and it turns out to be the value a variable holds when the config is generated. Records 0 to 6 are second, minute, hour, day, weekday, month and year, every one of them equal to the build timestamp in all 21 containers. A writer has to stamp them, or the remote's clock comes up set to whenever the old config was made.
And a hard key has its name, section 133, which is the part of that sentence which used to stop at the screen. The durations a record stores decode back into the bit frame the device sees, and a frame is a number that can be matched against a catalogue of named commands, where a duration stream could only ever be compared to another duration stream. Matching against the catalogue and button maps of the account that generated a config names the button a scan code belongs to: 32 buttons of a Harmony One and 36 of a Harmony 600, in reference/button-maps.md, read only and with nothing written to either remote, which is what section 48 said needed a write into a running remote's memory. It is a calibration instrument rather than a reader, since it needs the generating account. And the position of a key still does not follow: under the electrical column formula the digits 1, 2 and 3 of a Harmony 600 sit in columns 3, 2 and 2, so a matrix number is a wiring decision.
The same decoder answered a question that had been open since arch 8 closed, section 134: the second pointer group 37 infrared records carry is the same code with one biphase bit cell inverted, which is a bit the protocol makes the sender alternate between presses. It was found by the decoder refusing to read those records, they all belong to one device, and the two arch 8 configs contributed later have none, so the 37 was never a property of the architecture.
So a config now reads as what it is for: which devices, what each is called, which activities, what
each is called, which key starts it, which devices it drives, what each button sends and what the screen
calls it.
packages/codec/src/inventory.ts, one call, which is the shape FreeHarmony consumes rather than seven
readers it would have to order itself.
One config's device and activity counts are checked against its owner's own written description, section 124, which is the first ground truth this project has had for either: four devices and four activities, on a menu of five entries whose fifth is the remote's own settings page.
The infrared carrier is generated in software, not by a hardware PWM, with cycle-counted
delays and a per-half-cycle enable mask. The config supplies a 16-bit carrier period and an
8-bit duty value, scaled by value * 4 / 10 into instruction cycles. Cross-checked: 38 kHz
implies a stored 263, which the code's arithmetic turns into exactly 26.25 us.
Logitech's device catalogue reads locally and it names every command, section 229. A configuration
numbers its infrared codes and names none of them, so which of a device's ninety codes is volume up came
only from Logitech's button map service through two test accounts. The archived catalogue, 7889
manufacturers and 276236 devices, states a name beside every code: a device group is identified by the
numbers its own records decode to, 36 of 38 groups in this corpus and 31 of them on every number they
send, and 537 of 598 button bindings then get a name, all of them on the two calibration configurations.
It was tested before it was believed, and the closure is that the labels agree: Roku, VCR and Denon
are the config owner's own words, invisible to the archive, and they land on a Roku box, a Panasonic
video recorder and a Denon receiver.
A code stated as a name and a number becomes pulses, sections 152 to 169, which is what an
importer of Logitech's still-answering device database needs: their catalogue serves no pulse data,
only a protocol family and a frame value. The rhythm table in packages/codec/src/protocols.ts
holds 681 entries over 681 distinct families, one entry per family, of which
37 were measured here and 644 are Logitech's own definitions converted, sections 227 and 231.
The measured ones cover all 33 families this corpus holds a record of, section 233; the stated ones
are families no configuration here has ever carried, so codes: 0 on such a row is the honest number and
source is what tells the two apart. 33 of the stated rows carry a whole block derived
from Logitech's own statement of it, sections 228 and 233, and the other 611 carry a frame and nothing
after it, so a code of those families can be built and not yet written. It said "A stated row carries no
measured block either, so a code of that family can be built and not yet written" for one
day. What holds the 611 back is not the shape, which their definitions state and which reproduces all 29
blocks measured off their compiler exactly, but how many times a repetition is sent, which they state
for 39 of 684 families and which is not guessed here. That is now the only thing holding any of them
back, section 233: every other reason a stated row had no block has been read. It said "since one family appears at two carrier
frequencies" until 31 August 2026, which was true of the table and false about
Logitech: those two entries were two families their analyser both called SharpO1 48 Bit, and their
catalogue states one carrier per family. Each entry says which route measured it, most off configurations Logitech's own
compiler was asked to produce for appliances chosen here, and each family is named by their own
catalogue, matched on the rhythm their definition states. Their analyser turned out to be a decoder
of their own database rather than of infrared, accepting rhythms their compiler never emits, so it
was retired as evidence and kept as a second opinion. Biphase codes, where the bit is which half of
a cell carries, get their own reader and four families reproduce every record byte for byte.
A favourite channel is not a key binding, sections 154 and 156: it lands in four sections at once, and a channel with a leading zero is spelled out digit by digit instead of going through the number sender, so a writer chooses a mechanism per channel. Measured by compiling configs with favourites chosen for the purpose, which also gave base slot 16, the number sender, its first populated samples: seven made configs populate it and no found one does.
A television this project composed is byte for byte what Logitech's compiler writes, section
174. Their service compiled the same account twice, either side of their own generator adding the
television composeDevice adds here, and their infrared records for the same catalogue codes match
ours block for block once two of their generator's spelling conventions are adopted: every once
block opens with a lead in silence, and an over-long duration is spelt as maximal words with the
remainder balanced across the last two. Both conventions were measured before being adopted, and
the five remaining differences between their addition and ours are bookkeeping, written down in the
finding.
Moved out of CLAUDE.md on 29 August 2026, where it was a second copy of this document's own subject.
todo.md is the plan of record and tracks its own progress. Steps 1, 2, 4 and 5 are done,
and step 3 is done as far as the firmware can take it. This section is a status board, not a
summary of what is known: that is docs/findings.md, 365 sections, and docs/config-format.md
for the structured form. Section numbers below are the pointer into them.
The read path works, and flash has been written on five units, the first in section 222:
one 64 KiB block of the spare Harmony One's own configuration, erased and put back unchanged on 30
August 2026, verified over the block and over the whole configuration. This said "one write has been
performed" long after it stopped being true; the spare Harmony One, the Harmony 525 and the Harmony 650
have all been written since, and a Harmony 700 was sent one reinstall request, section 295, which
writes no flash from the host, and then had Logitech's 2.8 image staged into its external flash and
installed by its own safe mode image, section 297, which is the one write outside a configuration
region, decision 18, and then had one configuration block written back unchanged, section 300, and its Denon's power on delay
raised and put back, section 301, the change heard by an infrared receiver both ways; and the Harmony
600 had one configuration block written back unchanged after a full backup, section 302, and its KPN box's power on
delay raised and put back, section 303, which landed and was overridden by a delay saved on the remote. GET_VERSION, READ_MISC
and READ_FLASH run from our own host code on both bench architectures, a config read matches each
unit's lab dump byte for byte, and the four remotes this library could open when this sentence was written are fully read and verified
against their backups: user config, application firmware, safe mode, and the internal pages where the architecture
serves them, no differences. What is
verified is that each backup is faithful; restoring from one has never been tried.
Byte accounting is under "What reads" above, with the figures each architecture started from. A
second copy of that table stood here from 29 August 2026 until later the same day, brought in with
the text moved out of CLAUDE.md, and it is worth recording rather than quietly removing: the move
was justified as ending a two copies state and it created one in the destination. make facts could
not see it, because both copies carried markers and both were correct, which is exactly the state
this project's oldest rule is about. Two right copies are what precede two diverging ones.
Moved out of CLAUDE.md on 29 August 2026. Fourteen entries, of which twelve are open: two are struck through and answered, and they stay because a question that turned out to have an answer is worth as much as one that did not. Read when a session needs them rather than carried into every one.
GET_VERSIONfield 6, a compiled in0x0Cwith no reading, and field 9's accessor, a table read at program0x020024whose byte is0xDEwhile the remote reports0x16. The other ten fields have a reading, section 59 and section 87. The installed image is ruled out as the explanation: the One's own flash dump is byte identical to the package there, so what is left is what aTBLRDdoes past the on-chip flash, which is a hardware question and not a firmware one. Field 6 has a reading now and it is unconfirmed rather than absent, section 116: it names a platform, not an architecture, and arch 12 and arch 14 are one platform under it.0x0Con both of those across six images,0x09on arch 9,0x08on arch 8. What a recovery role image answers is per architecture, sections 118 and 190: a live Harmony 525 in safe mode reports0x00, as the arch 8 bootloaders do, and a live Harmony One in safe mode reports0x0C, the same as running normally. So section 118's "for an application image only" was one architecture written as a rule, which is what it had just criticised section 116 for in the other direction, and section 116's arch 12 figure turns out to have been right. Arch 14 is unmeasured on hardware. The mechanism is section 189's, that arch 9 copies its safe mode image over the application and arch 12 copies nothing, so the two findings predict each other. Field 4's low nibble is what actually says which state a remote is in, 0 running and 4 in safe mode, and on a Harmony One it is the only one of the twelve fields that moves between them. Everything else already grouped those two: same MCU family, sameGSPMcookie, and Logitech's own platform table calls arch 12 the Gin family. What moved it was the population going from four images to eleven; four could not tell "equals the architecture, except once" from "equals the platform, always". ThebcdDevicehigh byte has the same shape and different values,0x08,0x09,0x10,0x10, so the two are not one variable.- What the One's analogue channel 1 measures, section 103, and USB cannot settle it, section
111. Two readings fit and they differ only in the sensor's wiring, so the firmware cannot choose, and
the bench read that was meant to choose landed on outcome 2: the converter is off and its result
register frozen across 60 seconds while the clock ticks in the same poll, so covering the sensor
cannot move
0x110. What the read did settle is that the band, the state and the level in RAM agree with each other through the config's own base slot 15, which is how we know an arch 12 remote on USB has read its config. Finishing the sensor needs the remote off USB, which no read path reaches. Channel 0 is the battery,0x111, eight levels, and it reads 7 of 8 on a charging remote. - Arch 9 (Harmony 525) has no read path to its data memory at all, section 137, which the same round
established with a control worth copying: the
READ_FLASHwindow at top byte0x40answers zero for the bank 2 bytes holding the offset and buffer pointer of the read in progress, so the zeros are the window's and not the memory's.READ_MISCwas already dead there, section 90. So the reading of a Harmony 600 (arch 14) is the only bench measurement left of the two, and on arch 14 a remote does not load its config on USB at all, section 110, which would make a null result there mean less than it looks. - Which routine increments the minute on arch 12 (Harmony One), section 111, and the reason it was
never found is closed rather than the routine:
0x109is state variable 1, section 138, and every write to a state variable goes through a store whoseFSR0is computed from a literal 8 and an index. So there is no direct write and noLFSRbecause there is no pointer variable, which was the dead endtrace-sectionnames first and it was the right dead end for the wrong reason. What is left is which caller passes index 1, and nothing depends on it: the field is named from the firmware's own subtraction against base slot 3's record and its behaviour is measured twice. - The rate the arch 12 clock loses time, section 111. Two mechanisms are read and both only lose, so 5.6 minutes a day is an upper bound rather than a figure. Deliberately not measured, decided on 10 August 2026, since it would need the One left alone for a day and read at both ends, and no document or code anywhere wants the number. Recorded so that the bound is never quoted as a measurement.
- The arch 12 calibration words at
0x01F5C0and0x01F5C2, section 105: 94 and0xFFFFon both units, fetched by the same helper as the battery scale, consumer not traced. The scale itself is read,4 + trim/65536millivolts a converter count, and section 44's battery conjecture is a finding now. Two hazards worth carrying:0x01F580is on chip, so a firmwareTBLRDthere and aREAD_FLASHover USB at the same number read different memories; and the words had been in the lab for a day, filed as "unidentified" indocs/memory-map-one.mdtwo rows above the note that says two remotes differ at+0xF582. - Which I2C device sits at address 0x60 on the Harmony One, section 106. Thirteen channels of
three states, two eight bit level registers, an enable on
LATCbit 5 and no readback, which is the shape of an LED driver and most plausibly the keypad backlight, dimmed by the same band that dims the screen. Not confirmed and deliberately not named. A datasheet search on the address and the register numbers, or a photograph of the board, settles it; firmware cannot. Our frame decoder reads an unmerged pulse train and should not, merged now, section 164. Adjacent durations of one kind are one interval physically, so the reader merges them, and it costs 45 records of three arch 8 (Harmony 880) configs that all read the same eight bit value, which forty five different commands cannot be. Logitech's own decoder refuses all 36 of them it was asked about. It cost less than section 153 predicted, because section 163 landed first: the partition is 3502, 0 and 1128 rather than the 3502, 764 and 57 predicted, since requiring a constant non carrying half already refuses everything the merge would have made ambiguous. Two things to carry. The merge is not applied to the biphase reader, where two adjacent cells of one kind are two cells. And after it a biphase code can produce a plausible pulse distance reading, twoMagnavox 13 Bitrecords among them, so the rhythm table's join prefers a reading that lands on a number by value over one that lands only by matching a width.- A frame's non carrying half has to be one length, sections 163 and 165, which is the rule the
terminator constant had been standing in for. One length means it does not split, by the same ratio
the carried half has to split by, and not byte identical: exact equality refused ten records of the
compiled sample whose flat half alternates between 433 and 434, nine of which our reader now puts on
the exact number Logitech's own analyser states. The margin is 6.1% admitted against 100% refused, with
nothing in between.
decodenow demands it astimingsOfFramealways did, soGAP_UScould rise from 4000 to 8000 andJerroldO1 16 Bit, whose set bit is a 4505 space, is the twenty first entry in the table. It cost one direction of section 134's biconditional: the 148 records that read under both conventions now read under none, which is exactly the biphase population, so the detector moved to the reader that names the cause. 148 biphase codes are readable and we cannot read them, read now, section 162. A biphase code has one duration, the half cell, and the bit is which half of it carries, so there is nothing forirFrameto split and its two conventions both fit.biphaseFramesreads them and three families are in the rhythm table, each reproducing every one of its records byte for byte. The confirmation is that the reading, worked out on a configuration Logitech's compiler made for appliances chosen here, lands on 48 of 48 records of four contributed configs where their analyser had already stated the number.irFramestill refuses them, which is deliberate: that refusal is the corpus's only detector for the family, section 134.- Three of the four infrared encoding classes, used by no config in the corpus, so a firmware problem rather than a decoding one, section 42. Why they are unused is settled: Logitech's own user manuals say the learned signal was uploaded to their web site, which did the pattern matching and chose the storage form, so the class was a server decision and the unused ones are the ones that service never emitted for these devices. A miss was "stored as-is in its original format", which predicts a raw class. That matters for FreeHarmony: the service that made the choice is the discontinued one, so learning a code without it means making that choice locally.
- Where a learn session's samples leave the remote is read, section 98, and the two searches
that failed did so because both assumed the bytes are sent. They are not.
START_IRCAPclears two 66 byte buffers at0x0600and0x0642and a toggle at0x0684, the capture path fills whichever is open, and the transport points the endpoint 1 IN buffer descriptor straight at it,0x40Eand0x40F, with the count at0x40D. So no routine ever emits0x90: it is stored into the buffer at0x602. On arch 12 a report is 64 bytes,0x90, a sequence byte advancing by0x10, then samples as big endianu16durations differenced from CCP2, with the payload length repeated in the last byte. That encoding is the config's own, bit 15 marking a pulse, so what comes off the remote is already the shape a record wants. Arch 14 has the same header, written throughINDFbecause it reaches the buffers byFSR, which is why a scan keyed on the buffer offsets missed it; what stays arch 12 only is the differencing that makes a sample a duration. The reports are unsolicited, so a host must keep reading during the session; that settles section 91's disagreement between the two clients in the classic one's favour. Do not argue this from a literal scan: a data response code carries a computed length nibble and never appears as a literal, which cost one wrong negative here,reference/superseded.md. - The physical button map, meaning the matrix keypad. The Harmony One's touch panel is mapped,
section 125, and out of the config rather than the hardware: base slot 17's rectangles, the mode page
byte saying which page is in force, and a transform onto the display. That leaves the 40 keys around
the panel, and every other model; this said 44 until 9 October 2026, the drawing's count, which
includes the panel's four touch areas. Measured as far as USB allows and no further, section 48: a
remote on USB never runs its keypad handler, because USB mode's own loop does not scan the matrix. It does
run the rest of its application, section 111, and "never runs its application" was the wording here
until a Harmony One was watched ticking. On arch 14 it does not even load its config, section
110: the journal's five variables are zero on the 600, so neither the container's marker check nor
the allocator has run, and anything the host wants to know it computes from the bytes itself. On arch
12 it does load it, section 111, because the config is memory mapped and there is no load step.
Arch 14 yields the column
only,
(code - 1) mod 4, and arch 12 yields nothing at all, since sixteen buttons from every region of the One share one sense line. Finishing it over USB needs a RAM write to drive the rows, which the rails forbid, and that is not proposed here. The cheapest route needs no remote on the bus at all, and it is the board, section 144: a survey of an 885's circuit board, done by somebody else and checked here against our own configs, is what settled arch 8's lattice as 4 by 16 withscan = (line - 1) * 4 + input. The 885 config's column census and that board agree at 14, 14, 14, 13 through routes with nothing in common. The arithmetic is per architecture and must not be ported: arch 9 (Harmony 525) isgroup * 8 + column, arch 14 (Harmony 600 and 700) is 4 by 14, and searching all nine images this project holds finds arch 8's encoder in the four arch 8 ones and in none of the others. On arch 12 (Harmony One) the board is the only route left, since a live census there yields nothing. There is a second route that needs no write, section 123: the 525 implements infrared learning, so pointing the original equipment's own remote at it and matching the capture against the class 5 records section 82 read names the command, and the config already binds a scan code to it.0x70is still a command that changes a remote's state rather than reading it, soREAD_ONLY_COMMANDSrefuses it and nothing here has sent one. The refusal is that allow list and not the write flag, which this said until 29 August 2026: no method sends0x70, no rail mentions it, andHARMONY_ENABLE_WRITES=1would enable nothing, so whoever implements learning has to add the command, its classification and a rail rather than lift a switch. Neither of Logitech's own applications has it either, checked on 9 August 2026: a host names buttons and the firmware resolves the name to hardware, so no host ever held the map.docs/host-client.md. Arch 9 sits below both and needs no census, section 89: the 525 senses on a single line like the One, so a press is not even worth a column, and its matrix falls out of the firmware instead. 8 by 8, scan codegroup * 8 + columnrunning 1 to 64, and both its configs bind the same 50 codes, none a multiple of eight and contiguous in the resulting lattice to 57. So the 525 has fifty matrix buttons, predicted from firmware plus config and then counted on the remote, which makes it the one architecture where every matrix button is bound and no bound code lacks a button. Counted a third way on 11 August 2026 and it is fifty, from a product photograph, which is a free confirmation of a number that had cost a firmware read and a hardware census.reference/silhouettes/h525.svgis that count as a drawing, and what it does not carry is any scan code, because arch 9 (Harmony 525) has none measured at all: the positions are drawn and the assignment is open, since section 48 is why no read path here can produce it. Nor is it yet a usable map of where the keys are, since every key in it sits on a horizontal axis and a 525's rows do not, which is what the traced geometry fixes on the models that have it. The four soft keys are narrowed to the set{30, 31, 38, 39}and deliberately not assigned within it, because nothing establishes which of columns 6 and 7 is the left one. A test refuses adata-scanattribute anywhere in the file, so filling one in has to be a deliberate change with a measurement behind it. The 600 and the One are drawn too, and the pair is instructive about what a third count is worth. The 600 came to 54, which is exactly what section 17's field split and section 48's column census of 14, 14, 13 and 13 both give, so three independent routes agree. The One came to 44 and nothing can check it: arch 12 yields no column from a USB census because sixteen buttons share one sense line, so that number is a count of a photograph and the drawing says so rather than implying confirmation. Both carrydata-scanon their measured keys since the traced geometry landed on 21 August 2026: the 600 its 36 mapped buttons, the One its 32 plus the two touch arrows section 125 placed, which corrects the next paragraph's original ending in place. A scan code has a name now, read only, and it still has no place, section 133: the code a scan sends decodes back into the bit frame the device sees,packages/codec/src/irframe.ts, and a frame matched against the command catalogue and button maps of the account that generated a config names the button. 32 buttons of a Harmony One and 36 of a Harmony 600,reference/button-maps.md, with nothing written to anything. It is a calibration instrument and not a reader: it needs the generating account, so it works on the two configs Logitech compiled to our specification and cannot name a key in a contributed config. Three things to carry. The ambiguity was mostly a scope error, eight scans a remote down to four: a scan's command is per activity and its button is not, only anActivityButtonMapmay name an activity's set, and the assignment is globally injective because a button is one physical key. The four that remain are real, two up keys and two down keys sending one command each, and no decoding breaks a symmetry. So the tables stay out ofpackages/usb/src/models.ts, since the rest is unbound: these configs drive three devices in two activities and a library answering for a scan they never bound would answer from nothing. The silhouettes were going to get nodata-scanon the same ground and got them on 21 August 2026 anyway, for the measured keys only, which serves both: a drawn key answers with its code or with nothing, never from nothing. And the geometry does not follow: under section 48's own column formula the digits 1, 2 and 3 of a Harmony 600 sit in columns 3, 2 and 2, and no divisor to 19 in either direction puts a digit row on one line, so a matrix position is a wiring decision and a test asserts it cannot be recovered. MCU_IDis unreachable by construction, not a task: a PIC18 keeps its device id at0x3FFFFEand the internal read window is two 64 KiB pages. The arch 12 part number stays inferred.
Moved out of docs/plans/002-the-roadmap.md on 6 September 2026. It sat there as a second list of open questions
beside "What is still open" above, both with corrections written in place, and neither knowing about
the other. They are deliberately not merged into one list: the section above is about what a
reader or a writer cannot yet do, and this one is about what the firmware has not told us. Where they
overlap, the overlap is the interesting part.
The heading said "unchanged" until 25 August 2026, and by then five of its entries were answered in
this document's own body, which is the drift make facts cannot see: a list of open questions has no
number to recompute. Answered entries are corrected in place below rather than deleted.
- Three of the four IR encoding classes at the dispatcher
0x12F08. No config in the corpus carries one, section 42, so the firmware is the only evidence there will be. - The encoder from raw learned timings to a config IR record. This ran on Logitech's servers, so nobody had it. Mostly built since: a code stated as a name and a number becomes pulses through the rhythm table, sections 157 to 169, and the block spelling matches Logitech's own generator byte for byte, section 174. What learning still needs is the tail shape, section 152, and the storage class choice, section 42.
- Activity semantics. Closed: the accumulator machine is read, section 34, the screen interpreter is read, section 40, and a binding table entry is an activity's handler set in the four hop chain that starts it, sections 120 and 121, all fifty activities named.
- The LWJL difference between architectures. The other half of this entry, the translation from the scanner's linear index to config event codes, is answered: the codes carry the index directly, section 89 and the step 6 narrative above.
- Whether the firmware implements event injection over USB. Answered no on arch 14, in this
document's own step 6 narrative; arch 12 unexamined and nothing wants it. Queueing an action
instruction is another matter: the Harmony 600, 650 and 700 do it through command state
0x34, section 326, read and never sent. - What the log area holds. Base slot 2 is named, section 47, so the pointer table is complete. One of the five append cases is read, section 111: case 3's six bytes are the clock's own fields copied in descending significance, so its record is a timestamp. What remains is the other four, and why the region is measured in eight byte units on the three architectures whose firmware never reads it. Nothing in the corpus appends, so this is a firmware only question, like the three unused IR classes above, and on arch 12 it is worse than unused: both bench Harmony Ones already have the declared region written, so the appender disarms itself at the first attempt, section 111.
- Which activity a drawn name belongs to. Closed, sections 120, 121, 124 and 125: all
50 activities in the corpus carry their drawn name, through the modes an
activity's chain enters on three architectures and through the hit map on the Harmony One, and
make activitiesprints it. The sentence that used to stand here, that the tie from a name to an activity number was not read, was already contradicted by the M3 section above when the list's heading said "unchanged". A writer still has to build a font set and number it rather than look a character up, since the codes are assigned per config in the order characters first appear in the generator's string list.
Moved out of CLAUDE.md on 29 August 2026, where it was a rolling account of the newest findings.
Every claim in it is in docs/findings.md with a section number and a regression test.
It restates several findings that "Headline findings" above also covers, deliberately, because a
rolling account is ordered by when something was learned and that section is ordered by subject. The
figures common to both carry fact: markers, so make facts moves every copy together and they
cannot drift apart; what a reader should not expect is two independent statements of one measurement.
Recorded on 29 August 2026 after an audit found the move had also planted a second copy of the byte
accounting table here, which was a real duplicate with nothing added and has been removed.
Toggling codes are written the way Logitech's compiler writes them, twice per record, and codes with a release part compose, section 365. Some remote codes carry one bit the sender flips on every new press, so the device can tell a second press from a key held down. Logitech's compiles store such a code twice in its record, once as the catalogue states it and once with that bit flipped, and the firmware of the Harmony 650 and the Harmony One alternates between the two from one send to that device to the next; the composer wrote only the first. Some codes also have a part sent once when the key comes up, which Logitech keeps in the record's third slot: of the 27353 commands that have one the composer refused 25200 and wrote 2153 without it. Both are written now: eighteen catalogue devices composed and compared with the seventeen device groups Logitech compiled for our accounts give 774 of 797 records word for word as Logitech wrote them, where the rest differ for reasons other findings hold. Over the whole database 2063341 of 2067863 commands compose, and 73 of 54118 code sets have none that does. Where Logitech's definition and the measured signal shape disagree about what a press sends, 880 commands, the shape stays as it was. And the public archive's ready made waveforms, which the encoder had been checked against, are the archive maintainer's rendering of Logitech's definitions and not Logitech's own; the documents and comments that said otherwise are corrected.
The last eight protocol names the device composer refused now compose, but for 14 commands refused for a
conflicting repeat count, section 361. Four of them were misspellings in Logitech's database, Ada 40 Bit
where the protocol is ADA 40 Bit and three more like it, 221 commands that found no protocol and were
refused. Logitech's service gives every command a protocol
number beside its text, and on every command of the catalogue that number names the protocol the text spells,
exactly or but for capitals and spaces, so where the exact spelling finds no protocol the composer now looks
it up with capitals and spaces set aside; no two of Logitech's protocols are spelt alike that way. The other four were protocols whose measured signal shape did not fit some of their codes, for
instance a code stating three signals where the shape takes one, 87 commands; those are now built from Logitech's definition,
like other codes the measured shapes do not cover. A Pioneer receiver and a Philips television in Logitech's
Harmony One compiles hold three such codes, and composed they come out word for word as Logitech wrote them;
the rest are built from the definition with no compile to check against. Over the whole database 2038315 of
2067863 commands compose now, and 761 of 54118 code sets have none that does.
The device composer reads Logitech's infrared codes the way their own definitions say to, and 39074 more of the catalogue's commands compose, section 359. A code in Logitech's database is a protocol name and a number, and how many bits each part of the number takes is stated by the protocol's definition. The composer took it from the protocol's name instead, and a name like "Russound 9 Bit Quad" gives the number of base four digits, not bits, so 52658 commands of 156 protocols were refused as unreadable. Read the definition's way, 39074 of them compose; the rest are refused for reasons other items hold, and 172 are not infrared at all or are misspelt in the database. Three devices from a Harmony One compile, one of which composed nothing before, came out word for word as Logitech wrote them; those are protocols whose name was right and whose numbers ran over it, so the main case, a name giving the wrong width, is checked against Logitech only as signals, on two protocols in configurations whose devices are not known. Two smaller fixes came with it: 1217 commands of three protocols the name had read wrongly now send what the definition describes, and a long silence is spelt in the words Logitech uses. After it, 2038021 of the whole database's 2067863 commands composed, and 766 of 54118 code sets had none that did.
The delay between devices has been watched on a Harmony 650, and it acts in All Off as well as when an activity starts, section 335. Each device has a setting for how long the remote pauses between devices. With both at half a second, the Denon's command came 0.62 s after the KPN box's finished when Kijk TV started and 0.61 s in All Off. Setting the KPN box's to two seconds left the start at 0.62 s (that run's All Off was heard too poorly to time); setting the Denon's to two seconds made it 2.13 s and 2.12 s. So a device's setting delays that device's own command, a tenth of a second per step, and its wait seems to start once the device before it has sent, though that rests on something not measured. What was wrong before was where it acts: the configuration switches the delay on while an activity starts, and also during All Off and while the Help screens re-send commands, and a search for what switches it on had missed the last two. A device's own button pressed in device mode still waits for nothing, by the configuration's reading; that one has not been watched. Help pressed in device mode does wait, since it runs the Off screen's attempt to fix things.
An activity can now be added to a Harmony 600, 650 or 700 whose activity menu is full, section 316. The menu shows two activities to a screen, so it could only take a new one while its last screen had room, and a second composed activity on the Harmony 650 was refused. The composer now opens a new screen there, with the activity on its top row, the same background Logitech gives a screen holding one activity, and "3/3" in the corner with every earlier screen restated. Checked against Logitech's own work: a fifth activity added to the 650's four gives a third screen drawn the way Logitech drew the Harmony 700's third for five, and a fourth added to the 650's three gives exactly the two screens of Logitech's own 650 compile holding those three and one more. Built and read back on the 650's configurations with two and three activities added, every check passing; not written to the remote yet. One configuration still refuses, the calibration Harmony 600, because none of its screens draws the background a screen of one activity needs.
A device can now be added to a Harmony 600, 650 or 700 whose device list is full, section 312. The list of devices on these remotes shows four, or two, to a screen, and a seventh device on the Harmony 650 has nowhere to go on its two row list, so it was refused. The composer now opens a new screen at the end of every full list, with "4/4" in the corner and every earlier screen restated from "n/3" to "n/4". Checked against Logitech's own work: a fifth device added to the Harmony 600's configuration, whose lists are all full, gives screens drawn the same way as the ones Logitech's compiler drew for the 650's five devices, down to where each piece of text sits. Built and read back on the 650's current six device configuration with every check passing; not written to the remote yet, and the script that would write it stopped on that configuration's build timestamp, which Logitech wrote with a day of month of 0 on the 1st of October and our reader refused. Section 322 found the day counted from 0, so it reads now.
The list that shows two devices to a screen is one nobody can open, section 326. Logitech's compiler writes it into every Harmony 600, 650 and 700 configuration, wired like the others, and nothing on the remote leads to it: no button, no screen and no event names it, and the firmware only opens a screen something names, short of a command a computer could send over the cable. The lists people see are the four to a screen ones under "Devices". So the new screen a seventh device opens on the 650 matches Logitech and cannot be looked at; seeing a new screen open on the remote needs a list going from four devices to five.
A delay saved on the remote itself wins over the configuration, section 303. The Harmony 600's KPN box was given a wait of 4.5 seconds instead of 1.5 and the remote kept waiting 1.0, the same to half a millisecond on the infrared receiver. The remote keeps a small table of saved delays, and the configuration Logitech compiles copies that table over its own values when the remote starts: seen across three starts on the 600, and the 650 and 700 carry the same programs. The 600 had two such delays saved, from before this project touched it, and the Harmony 650 and 700, whose changes did take effect, had none. So an editor for these remotes has to read that table before it can say what a delay is. A computer can read and replace that table over the cable, section 304: the firmware of the 600, 650 and 700 answers a version request carrying a payload as a settings command. Reading the 600's table that way gave the same 41 values as the copy of its memory in the lab, both saved delays included. Reading the firmware also showed the library's read only filter would have let such a write through, which is fixed. Writing it works, section 305: four writes emptied the 600's saved KPN delay, the table read back exactly as predicted, and the remote's own lookup now reports the slot empty. After a battery pull the remote holds the configuration's 1.5 seconds in memory where it held 1.0, so the clear worked; the bench test's gap grew by only a tenth of a second against the half second, which is not explained yet. With the configuration's delay then raised to 4.5 seconds, the gap grew by 3.01 seconds for the 3.0 written, so the configuration's delay governs on that unit once nothing saved stands over it. The Panasonic television on that remote sends its power on and off codes seven times where every other code goes three times, and the account explains it: Logitech's catalogue holds those two for one second, which its service copies onto an account and its compiler turns into copies: seven copies start inside the second when each is timed at the 143.3 milliseconds the code family's definition states, and an eighth would not. Predicted before the Harmony 650 was programmed with the same television, and the 650 came back with the same two codes at seven, word for word, and every other at three. Twelve more devices with holds from 0.3 to 15 seconds, put through Logitech's compiler, and a fourth round chosen to tell three explanations apart, show one rule: as many copies as start inside the hold, timing each copy at the length Logitech's own protocol definition states rather than the length it is sent at, sections 306 to 308. It gives all 27 of these long press versions. The television's codes are sent 7 to 9 ms shorter than their family's stated length, which is why they get one copy fewer than a plain count would give. One record was predicted ahead that only this rule got right, and a second reading, whole copies for the Panasonic codes alone, still fits as well. The library now builds such a record from the catalogue's code and hold, and it comes out as Logitech's compiler wrote it, word for word, on 26 of the 27; the 27th is a family whose press it cannot build yet. A device composed with its catalogue power steps sends those records when it switches, and building them found our general block speller ending one family's codes with two words in the wrong order, now fixed and checked over every block in the corpus, section 309. On the Harmony 650 itself, pointing the television's device mode Power On key at the long press version, one byte, made it switch the television on. At the bench the television itself needs its power button held for about half a second, four repeats, so Logitech's second is a margin of about two; the 650's device mode Power On, three repeats, leaves it off.
A changed setting on the Harmony 700 is heard on the air, section 301. How long the remote waits after switching the Denon on was raised from six seconds to nine, written to the remote, and put back. An infrared receiver on the bench timed the activity each time: from the Denon switching on to its input command took about 6.6 seconds, then 9.6, then 6.6 again, three seconds each way, while the television's gap, whose wait nobody touched, stayed at 5.4 seconds throughout. It is the Harmony 700's first written configuration change, and the first change on any remote whose effect was timed by an infrared receiver; earlier ones were watched on the bench or read out of the remote's memory.
A composed activity on a Harmony 600, 650 or 700 now gets a device list of its own, section 294. Pressing the key under Devices during an activity shows the list of devices with that activity's own devices first and "Activity" at the bottom, which takes you back. A composed activity used to show the list meant for when nothing is running, with "Activities" at the bottom. The rule behind it holds on all thirteen real activities: rebuilt from the idle list, each of their lists comes out identical to the compiler's on the screen, 21 pages of 21. Written to the 650 for its own "LG kijken", and on the remote the list shows LG and Denon first, then PS3, KPN, Kodi and TV, with "Activity" leading back to the activity. Since section 352 that list is built rather than copied: its rows, the lists they run, its own key entries and every page, the word "Activity" included, so it no longer needs another activity's list to borrow the word from. Checked against 78 of the 82 activity lists in Logitech's compiles, the other four being a French configuration the English builders refuse; each device's label and the order of the devices the activity does not use are still read off the idle list.
The three screens every Harmony 600, 650 and 700 with activities has once are built whole now, section 356: the list of devices the centre key opens while nothing runs, the activity menu, and the "Turning system off" screen All Off shows. Their key maps, page lists and the second copy of each, and the lists their rows run, used to come out of a Logitech compile around the pages the composer drew; now they are generated, and rebuilt this way 22 of Logitech's compiles come back identical, including when every byte the builder claims to generate is blanked first. The activity menu follows the setup file's order. "Do you want to turn off your system now?" turned out to be a Help screen and not part of Off. Still read off a configuration: the rows' labels and fonts and the pictures; where the texts drawn by reference sit is generated since section 358.
The remote's own screens are built whole too, section 357: "add an Activity on this button", "USB Connected", the battery screens, "Update Successful", the learning screens and the blanks between them, fourteen on a Harmony 600 or 650 and nineteen on a 700. Rebuilt from a description, 21 of Logitech's compiles come back identical, again with every byte the builder claims blanked first. Two things turned up: "USB Connected" has a three key sequence that leaves it, though letting go of a key resets it, so how a person completes it is not read and nobody has tried it on a remote; and the variable the Harmony 700's first screen sets is its low battery flag, which section 311 had left open. One more owner's 650 is refused because its letters cannot be read as an "I", and a French one for its words. The welcome tour can be left out: in the form Logitech skips it, nothing enters its ten screens, about 1700 bytes; its seven byte start list has to stay unless three other lists change with it.
Every text on those screens is generated now, section 358, on the Harmony 600, 650 and 700: what it says,
where it sits and whether it is written out in full or points at an earlier copy of the same word, which is how
a configuration saves room. The last of those turned out to be one rule for the whole file rather than one per
screen: the first time a word appears it is written out, and every later use points back at that first one, on
every one of 63992 texts in 24 of Logitech's compiles. Rebuilt this way, 22 of them come back identical. The
check that every byte was really rebuilt is narrower than it first read: where each text sits is read to tell
what it is, a title, a corner label, a row, and then placed again by the rule. A French Harmony 650 is
refused, since its words do not fit the English screens. The test setup composed for the bench, run through
it, shows a person nothing Logitech's own compile of that setup does not, apart from the menu's order, and is
24 bytes shorter. It also found one thing every earlier composed file had wrong: "Starting Plasma kijken"
broken over two lines where Logitech draws it on one, because the limit was set at the narrowest width the
evidence allowed. The new limit is measured on the Harmony 650 and assumed for the 600 and 700. Not
generated: the letters and fonts themselves, todo-compile-650.md 8.2, and the texts on help, the Remote
Assistant, the tour and the status screens, which are not built.
Six of a Harmony 650's pictures are built now, section 363, and the four backgrounds todo-compile-650.md 9.1
names are not among them. A 650 configuration holds 18 or 19 pictures, each the same on every compile that
holds it. The plain dark background, the bars along the top and the bottom, and three small patches drawn in the
bottom right corner are a fill or a band of one coloured rows, and built from that rule they are Logitech's bytes,
about 41 KB, the same six pictures on all 13 of Logitech's 650 compiles; all 22 compiles of the 600, 650 and 700 come back identical
with them built, again with those bytes blanked first. The curved grey and red backgrounds with their cross or
line, the start up picture, the corner mark and four firmware screen pictures are designed images: their pixels
are not written into this repository, and a configuration still takes them from a Logitech compile until it is
decided where they come from. The cross is not simply a line drawn over the plain curved background either:
the two differ in 98 pixels beside it, each one colour step off. Two black dotted pictures belong to Help's delay screens and are left out with
Help. The assembly of section 362 builds the six since section 364, one more step after its texts.
The remote's own wiring calls nothing copied any more in the configuration this track builds, section 360. The lists behind the remote's own events, the screen light, the battery screens and starting up are a tree of small tests on the firmware's own settings, 40 lists on a Harmony 650, and they are generated now; 23 of Logitech's compiles, the one with no activities among them, come back identical, again with every byte but the few the description reads blanked first, apart from the file's frame, which says where each piece sits and how long it is, and the model. Three lists the wiring calls are not built, because each belongs to something this track leaves out: putting back delays saved on the remote, the Remote Assistant's check, and what the key read as All Off's runs first so Help can offer to fix a device that did not switch inputs. Left out, the configuration does not touch the remote's saved delays at start and that key switches straight off; that is a reading of the files, in none of Logitech's, and not yet tried on the 650. The test setup recomposed that way is 11 bytes shorter and shows nothing different on the menus, start screens and keys the comparison reads; it says nothing about the All Off wizard, Help, the Assistant or the restore, which are what changed. Section 347's count of what it did not build missed that key's eight lists, and is corrected.
A setup file and one Logitech file now make a whole Harmony 650 configuration in one step, section 362. The chain of lab scripts that built each composed file is one function, which takes the setup file and names the Logitech file it still borrows from as an input of its own, so the finish line can swap it and count what is left. Given Logitech's own compile of the test setup, the result shows a person the same as that compile apart from the menu's order, and the same as the file the lab's scripts composed for the wiring step, which was never written to the remote. It is 1723 bytes longer than that file: Logitech's version of Plasma kijken with its Help screens against ours, less the 70 lists that file carries and nothing uses, plus a little because the two started from different Logitech files. Those 70 are dropped now, which needed every place in a configuration that names a list: ten kinds, and one of them could be left out without any comparison of what a person sees noticing, so the dropping checks itself. Our generators lay about one byte in fourteen as content, and about one in seven counting the addresses they rewrite inside Logitech's pieces, which is one in nine and one in five and a half since sections 363 and 364; the rest is Logitech's, and most of that is pictures, infrared codes and fonts. What the finish line still needs that no step names: a starting file with no devices and no activities, since nothing here can take one out, which section 364 found the composers cannot start from, and a setup file that says what goes on an activity's screen, which today's does not. Measured on the Harmony 650 only, and not written to a remote.
The assembly now composes what the starting file lacks, section 364. It adds every device and activity the setup names that the Logitech file it starts from does not hold, with the composers that built the bench files, and builds the six pictures of section 363. Started from Logitech's compile of the four activities, it composes Plasma kijken from the setup file and comes out as the file the lab's scripts composed would come out of the same steps, byte for byte but the date it was built, and shows a person what Logitech's own compile of the setup shows, the menu's order aside. The setup file does not say what goes on an activity's screen, so that is handed in beside it; without it, the activity's four screen items are missing. The two smallest Harmony 650 compiles in the lab, one and two devices and no activity, take none of it: on the two device one the device composer cannot recognise the activity menu without an activity in it, its fonts lack a K and the word Eject, and the activity composer needs another activity to tell its records apart; on the one device one every device is refused because there is no list that switches all devices off to join. So the finish line needs either a way to take devices and activities out of a file, composers that manage without an activity, or a starting file built from nothing that has what they need, plus the fonts and the eleven designed pictures. About one byte in nine is ours as content now, and one in five and a half with the addresses we rewrite.
A Harmony One's activity menu can now grow a page, section 293. Its menu shows three activities to a page, and a fourth used to be refused. Now it opens a new page, and the menu says so in the three places a Harmony One configuration does: "2 pages" at the top, "1/", "2/" on each page, and the two touch keys beside the display no longer switched off, which is what every Harmony One screen of several pages in the corpus does. Written to the spare Harmony One and it pages: ten activities over four pages, "Test Een" and "Test Tien" at the end, and the television switching on from the fourth page. The same work found that the device lists given a third page earlier said "2 pages" at the top, and that write corrected them to "3 pages" on the remote. One thing it showed that no test sees: a label whose characters the menu's font lacks comes out in another colour.
How long a Harmony 650's screen stays lit is a timer in its configuration, section 292. Logitech's software called the setting GlowTime and offered it for the Harmony 600 and 700; on the 650 it was 8 seconds, and the screen went dark after six or seven. Writing 20 and then 10 made the screen stay lit for about 20 and then about 10, so it is a setting FreeHarmony can offer. Measured on the 650; on the 600 the one configuration compiled beside a saved copy of the setting agrees with it, and on the 700 two timers look alike and which one it is stays open. After the write of 20 the remote once kept a black screen after unplugging until the batteries came out, with its configuration intact; that did not happen after the write of 10 and is recorded rather than explained.
And a composed activity runs on the Harmony 650, section 291. Written whole, 14 blocks, and every one of eight checks made before the write held on the remote: the row on the activity menu, the start up screen, the television and the receiver switching on and to the right input, the working screen with its four corners, the keys, the way into device mode and back, and Off. That is the arch 14 counterpart of what the spare Harmony One showed in section 279.
And a composed activity on a Harmony 600, 650 or 700 now shows its own two screens, section 290. On those models each activity has a screen of its own while it starts, "Starting" and its name above "Please keep the remote pointed at your system", and then a screen of its commands, which turns out to be the same page a device's commands are shown on, with "Devices" at the bottom instead of "Back". The composer now builds both, and also teaches the remote's lookup tables which screen and which keypad map belong to the new activity, so the key under Devices and the way back from the device list both know about it. Built and read back on the 650, 600 and 700 configurations with every check passing, and the drawn screens look like the real ones. Three limits: a composed activity's key under Devices opens the list shown when nothing is running rather than a list of its own, the start up title can only use letters the remote already draws in that font somewhere, since each font holds only the letters its configuration uses, and the "Remote Assistant" question the other activities ask is left out. Not tried on a remote yet.
And a composed activity gets a place on the activity menu of a Harmony 600, 650 or 700, section 289. Those menus show two activities to a page, one per row, and each activity answers to both buttons of its row. That turns out to be exactly the two row layout the device list already uses, so the composer now shares the step that adds an item to a page between the two menus. Only two things differ: pressing the row selects the activity rather than opening a device, and a full page has a background of its own, which a page holding one activity has to take once a second activity joins it. Built and read back on the 650, 600 and 700 configurations with every check passing; the calibration 600 is refused because its only page is full and adding a page is not built (built since section 316, which refuses the calibration 600 for another reason: none of its screens draws the background a new page needs). It has not been pressed on a remote yet. Looking at the activities for this also showed that every one of the thirteen opens with a "Starting" screen of its own, and switches on, for the length of its start up, the setting every command's delay step tests. So the composed activity will have to do the same, or the delay between commands built earlier will not act; whether the power on delay needs it too is not known.
And a device composed for the Harmony 600, 650 or 700 now waits between commands the way Logitech's own do, section 287. Every command their compiler writes for those models first runs a small delay step, which by every sign is the device's own inter device delay, applied while an activity is starting up and not when a key is pressed in device mode; the remote has not been watched doing it. Watched since section 335, on the Harmony 650, and it acts in All Off and Help too, not only while an activity starts. That step is three short lists per command, two of them belonging to that one command and the third to the device, and a table per device that turns its delay setting, 0 to 2 seconds, into what the remote queues. Measured on all 1598 commands of the five configurations of those models, and the composer now writes all of it for a new device, with the half second Logitech gives nearly every device. It has not been on a remote yet, because it only acts while an activity starts, which is what the composed activity will show. A device's power on delay is composed too now, section 288: switching the device on sends its power code and then makes the remote wait the device's power on delay, up to 45 seconds, before anything else goes to it, which is what all fifteen of Logitech's own devices with a power switch do on those configurations. Neither delay has been seen on a remote on these models, and that they act only while an activity starts rests on the Harmony One's measurement and on these configurations, which is what the composed activity will test. Both have been seen on the Harmony 650 since, the power on delay in todo-compile-650 3.5 and the delay between devices in section 335, which also acts in All Off and Help.
And the Harmony 650 no longer opens Logitech's introduction tour after a write, section 286. Every write reloads the configuration, and every reload started a ten screen tour that had to be pressed through. One instruction in the configuration starts it; changing it to do what the tour's own Exit does leaves the remote exactly where a finished tour would, and after that write Danny saw the ordinary Remote Assistant screen instead. The changed instruction is exactly what Logitech's own software writes on the Harmony 600 and 700 configurations here, whose tour is switched off that way, so the edit copies the vendor rather than inventing something. The editor finds the instruction by its shape rather than by number, and tells a tour that is shown from one that is already skipped.
And a device can be put onto a Harmony 650's screen, section 285, and it works. The 650, 600 and 700 draw their screens differently from the Harmony One: four labels in the corners, one beside each button around the display, and a device list that is sometimes two rows instead. The composer now builds that, and the LG television composed into the 650's own configuration gets two pages of its own, a key map that binds volume, channel and mute to it and every other key to nothing, and a place on all five device lists, with every check passing and the result drawn. Where it differed from what Logitech's own software writes, in three small ways, it was brought in line. It was then written, thirteen erase blocks read back identical, and on the remote every one of the six predictions held: the television appears where predicted, its two pages and its keys work it, and Off still switches everything off. After every write the 650 opens Logitech's introduction tour, which the next write can switch off. Reading the 650's device lists also showed that an older reader picks the wrong screen for one device on the Harmony 700s, which is open.
And the firmware keeps more of the remote's state for itself than the rules said, section 284. The first eighteen state values are the firmware's on the Harmony One, the 600, 650 and 700 and the 880 and 885, where the rule had said thirteen: the thirteen came from the Harmony 525, which really does let a configuration use most of the rest. A writer now refuses the right block per model. Nothing written so far touched the difference.
And the Harmony 650 took a changed configuration, section 283. One device's power on delay raised from six seconds to nine and put back, each a two block write with everything read back. On this model a delay is a setting the remote holds in memory, and the firmware this unit runs reads as though a restart keeps the old one; newer firmware for the same model forces a reload. It did not keep it: the new value was live straight after the restart, both ways, read out of the remote's memory. A restart with nothing written does the same: after an activity was started, restarting put back everything the activity had changed. Every restart also set the remote's clock back to the time stamped in the configuration, so a writer for this model has to stamp it. The putting back is, on this unit, the firmware reloading the remote's state from the configuration, shown on a later restart at rest, although the checksum that is supposed to prevent a reload read valid on the read before; why is still open.
And Off switches it off, section 280. A device goes off when the remote writes 0 into its power state, and Logitech's compiler puts every device into two places that do that: the list Off runs, and every activity's start sequence, which sets each device on or off. A composed device was in neither. It is in both now, and a new activity sets every other device off. Written to the spare with the Denon in the activity, and pressing Off switched both off.
And it looks like an activity now, section 279. A Harmony One activity shows two screens: a start up one while the commands go out, and then its own, with blue pads, "Devices" in the corner and the header. The composer made neither, so the activity ran inside the television's device page. Which screen an activity runs on is stated by one table, the one device mode's "Activities" key uses to get back, and it has a case for all 60 activities in the thirteen Harmony One configurations here. The rebuild from the composers alone, with nothing added by hand, was written to the spare and does everything a real activity does on screen. What it still does not do is switch anything off: a composed device is in no activity's power off list and not in the one Off runs, so Off ends the activity and leaves the television on, measured, which section 280 then fixed.
And a composed activity switches the television on, sections 276 to 278. Written to the spare Harmony One four times over before it did, and each failure was a different rail. The first write put the activity's power variable above the storage the configuration declares, where the remote paints it over at every boot, section 276. The second put it where it belongs and still nothing happened, section 277, which looked in the wrong place: the variable changed as it should, and the activity's transition fired. What never left the remote was the command itself. Every command a Harmony configuration sends is followed by a small instruction naming the same device again, all 4267 such lists in the user configurations, and the device composer wrote the command alone. That answers a key press, which is how section 242 checked it, and sends nothing when an activity's state transition runs it. Paired, it works from both, section 278. Why the firmware treats the two routes differently is not read yet.
And it can now be started, section 275. The behaviour half of an activity leaves something the remote can run and nothing that reaches it: what reaches it is a row on the Harmony One's activity menu, which is now composed, drawn and checked by picture as well as by test. A whole activity built from nothing appears on the factory configuration's menu under its own name.
The correction on the way there is worth more than the row. The plan was to reuse the builder that adds a row to the device list, since the two menus draw their rows on the same grid. They are not the same page. A Harmony One's touch panel is bigger than the picture on it, and four keys around the edge of that picture are not part of it: two beside the display, which turn a list's pages, and two below it, labelled by whatever the screen draws just above them. In the activity menu both of the bottom two are live, "Options" and "Devices". In the device list only the left one is, "Activities", and the right one is not enabled at all. So the two pages offer a different number of tap areas, the old builder refused every real configuration, correctly, and there are two builders now, which is what Danny asked for.
The reply that reported this first said a device list page had "one arrow to the next page", which is wrong: Danny corrected it from the remote, and the file agrees on every point. Not one screen in any of the four configurations binds the two page turn keys, 778 pages of them, so paging is the remote's own business and never the page's. What had been read as a page turn is the left bottom button, and it had been carrying that name in a constant, in four comments and in a test's title.
It also answered a question that had been left open. A page holds three activities, which nobody had counted, and the everyday Harmony One's eight sit on pages of three, three and two. A document here had concluded from those numbers that the generator was not simply filling pages in order. With the capacity known, that is exactly what it is doing.
A configuration can now be given a new activity, and the rules it has to follow are measured, section 273. An activity is what a Harmony calls "Watch TV": pressing it switches the equipment on, sets the inputs, and points the keypad at several devices at once. Building a device into a configuration has worked since August and a television answered it; building an activity did not exist at all.
The rule that was missing is how an activity gets its number. The configuration keeps one counter whose value says which activity is running, and this project had written down a description of it rather than a rule: the value meaning "nothing is running" is the highest one, in ten of the eleven configurations we had, and the eleventh was called odd. Over fifteen there is one rule. The counter runs from zero with no gaps, its values are exactly the activities plus one for idle, and where the idle one sits is the part that varies, highest on twelve and somewhere in the middle on three. The three were treated three different ways before: one was named as the exception, one was written up somewhere else as evidence for the old reading, and the third had never been noticed.
Four smaller things came out of checking it, each of which a builder would otherwise get wrong. Where an activity's entry goes does not matter, so a new one can simply be added on the end: of the ten configurations with more than one activity, not one keeps them in numerical order. A Harmony 600 needs two keys per activity where every other model needs one, both of them presses of different buttons, so a builder that adds one leaves half that remote's menu dead. The leftover entry this project could previously only describe as "the one nothing points at" turns out to be recognisable on its own: it is the only one with no "leaving" handler. And that handler may be empty on two of the four models, so a builder has to emit it but need not give it anything to do.
composeActivity is the result, checked on one configuration of each model. It adds thirty seven
bytes and every reader in the codebase sees the new activity afterwards. What it deliberately does
not do is put a row on the screen, which is the next piece.
The rest of the 525's settings area is now read, and two thirds of it is not empty, section 270. The area set aside for settings is five blocks of 64 kilobytes and the settings themselves fill less than one. Reading the other four found the tail end of an earlier set of settings sitting in two of them, 8629 bytes that were never wiped. The reason is how flash memory works: it can only be cleared a whole block at a time, so writing something shorter than what was there leaves the far end of the old one untouched. We already measured exactly this on the Harmony One, where it is 408034 bytes, so it is how writing to flash behaves rather than a quirk of either remote.
Two consequences. A dump of that whole area carries settings nobody meant to hand over, which is why it stays in the private lab and never in the repository. And the two blocks at the top are empty, which makes them useless for practising a write: clearing a block already fills it with the same value, so writing that value back proves nothing. The two blocks with old data in them are the ones worth using.
The Harmony 525 has been written to, section 269. One block of its own configuration, 64 kilobytes, erased and put straight back, so the remote ended up holding exactly what it held before. That is the same first step the Harmony One got: a write whose right answer is known before it starts, so the only question it asks is whether the write landed at all. It did, and the whole configuration read back afterwards matches the copy taken in August byte for byte.
Two things came out of it that are worth more than the write. The erase really does clear 64 kilobytes and no more, which until now was something the firmware said rather than something anyone had checked. That matters on this remote more than on the Harmony One, because the block immediately below the settings is the remote's own operating software, and nothing inside the remote would refuse an instruction to wipe it. And the remote did not restart, where a Harmony One always does: the Harmony One runs its settings directly out of the memory being erased, and the 525 keeps them on a separate chip. That was what the existing model predicted, so it is a small confirmation that the model is right rather than a surprise.
One number in the safety code turned out to open three doors, not one. Allowing writes to this remote meant adding it to a list, and that same list was also what kept two unrelated commands away from it: restarting the remote, and writing to its working memory. Neither has ever been examined on this model. A test noticed immediately, which is what it was written for, and the three now have a list each, so permission to do one thing no longer quietly grants the other two.
Before that, the Harmony 525 became ready to be written to, section 268. Before anything can be written safely we have to be able to answer two questions: which remote is on the cable, and what is currently in the part of its memory we would change. Both are answered now.
Telling remotes apart was the interesting one. Every Harmony stores a serial number of sorts, and our code looked for it in one fixed place. The 525 does not have that place at all, and instead of giving us the wrong bytes it flatly refused, which is the best possible outcome: a wrong answer would have been compared against with confidence. It keeps its identity in a small separate memory chip on the processor, and the open source tool for these remotes had that written down all along.
The second question needed a new kind of read. We had the remote's settings, 51195 bytes, but memory is changed a whole 64 kilobyte block at a time and the settings do not fill one block. So there is now a tool that reads a stated stretch of memory rather than a settings file. What makes the result trustworthy is that its first 51195 bytes are identical, byte for byte, to a reading taken off the same remote a month earlier by completely separate code.
The rehearsal then ran on the remote and reported that writing the block back would change nothing, which is exactly what a first write should be. Nothing was written. Whether writing is refused was checked in a test rather than on the remote, deliberately: doing it on the hardware would mean turning the write switch on for a remote nothing has ever written to, and if the safety check failed we would find out by damaging it.
We now know how a Harmony 525 erases and writes its memory, and the news is a warning, section 267. The remote stores its settings on a small memory chip, and a chip like that cannot be changed a byte at a time: to alter anything you first wipe a whole block of it clean. Nobody here knew how big that block was, so nothing could be written to a 525 at all. Reading the remote's own program answers it: the block is 64 kilobytes, and the chip holds exactly eight of them. The open source tool that already writes these remotes says the same thing, from a completely different place in its code, so the number has two sources rather than one.
The warning is what sits in the other blocks. Two of the eight hold the remote's own operating software, one of them the emergency copy that is the only way to put the other back. The remote will happily wipe either if it is asked to. It checks that the address is somewhere on the chip and checks nothing else, so a wrong number is not refused by anything in the remote. On a Harmony One the remote itself stops you; on a 525 the only thing between a mistake and a dead remote is our own code. That is now written down as a hard limit, and a 525 still refuses every write until one has actually been demonstrated.
Writing turns out to be slow but simple: the remote sends a complete instruction to the chip for every single byte, so there are no awkward boundaries to line up, just a lot of round trips.
Every stored code in the corpus can be read now, and 411 of them could not be, section 266. Some appliances send a command by making the pulse long or short, and others by making both halves of each step the same length and putting the pulse in the first half or the second. We read both kinds. The second reader had a requirement nobody had written down: it needed to be handed a recording with the opening silence already cut off, and if the silence was still there it gave up and returned nothing at all. Almost every stored code has that opening silence.
Nothing had noticed because the three places in our own tooling that use that reader all cut the silence first, so it looked like the normal way to use it rather than a rule. What found it was asking the reader a question a different way round, from section 265's work on the Harmony 300.
The proof it now reads them correctly comes from outside. The set top box in Danny's living room appears in four different remotes' configurations, and its codes now match Logitech's own list of codes for that exact box: 44 of 44, 46 of 46, 51 of 51, and 55 of 58. What comes back is a keypad you would recognise, the ten digits, the four colour keys, the arrows, Guide, Teletext, Radio, Record, Pause. No volume, which is right: a set top box has none, the television has it.
That also takes back something section 261 concluded. Every stored code on one of the Harmony 350s was unreadable, and we said the only way to learn what those codes do is the list of names inside the file itself. All of them read now.
The naming tool still cannot use them, and checking why turned up a number that was flattering itself. It reports identifying 36 of 38 sets of codes. There are 51 sets in the files it walks: it reads 38 of them, and the other 13 it skips before counting, so they never appear as failures. The honest score is 36 of 51. Five of the thirteen are readable now, and the missing piece is small rather than deep: how many bits each family's codes carry was measured a while back and written into a document instead of into the code, so the tool has nowhere to look it up.
And the check that led to this was not new either. Matching these codes against Logitech's own list was done before, on a different set of files, and it is what established how wide this family's codes are in the first place. The write up first presented it as fresh, which it was not: what is new is the reader defect.
Programming that same Harmony 300 refuted the finding before it, and named which stored codes belong to which device, section 265. Danny put four devices on it, one on each of its four device buttons, a set top box, a DVD player, a television and a video recorder, and eight predictions were written down before the read. Six held.
The failure worth having is this. The read before it found one part of the file holding nothing where the other model holds two entries per device, and that was written up as a difference between the two models. It is not: this remote now holds eight entries for four devices, exactly like the other one. The file that held nothing was built in 2011, fifteen years before every other one here, and it was also the only one from the second model, so the model got the blame for the compiler's age. Every configuration states the date it was built, so the check was available and nobody made it.
And a stored code set now has an owner that was measured rather than guessed. Each set was identified independently in Logitech's own catalogue of equipment, by the code numbers in it, and three of the four came back as exactly the appliance Danny had assigned. So a device's code set is picked by which of the four buttons it sits on, and not by its place in any list, which is what the two previous readings had both assumed. The list in the file is alphabetical and disagrees with the buttons.
The remote also carries four favourite channels, and what was first written about them here was invented. This said they were channels nobody set up for it, and concluded that a favourite belongs to the equipment on the account rather than to the remote. Danny set them up on this remote himself, and they are exactly the four he entered. He had described the devices he assigned and said nothing about favourites, and that silence was read as an absence, which is the same defect as the one two paragraphs up with no file to catch it: what somebody did in a client is recoverable only by asking.
What it does buy is a check with a known answer. Four channels were chosen in advance and the file states those four, on a second remote of this generation, which confirms how a favourite channel is stored rather than showing anything new about it. The two remotes carry four and five because four and five were entered.
One thing this project had wrong about the product got corrected by Danny mid-read. The prediction document said this model has no activities at all, and used that to rule out an explanation for a spare entry in the file. He withdrew it: the remote does offer a shortcut that switches several devices on at once, without changing what the buttons do, and Logitech's own word for that is a partially set up activity. So the explanation is back on the table, and the lesson is the standing one, that a measurement over the files answers what a file contains and never what the product does.
A second model on the same generation turned two readings into rules and killed a third, section 264. Everything about that generation rested on one remote until Danny put the Harmony 300 on the bench, and Logitech separates the two models where it counts: four devices against eight, no long press against long press. Reading it, with no new code, settled three things. The room a configuration reserves for equipment is the model's own maximum, four on this one and eight on the other, where every other Harmony here reserves exactly what the configuration uses, so a writer for this generation sizes that table by the model. One part of the file holds an entry per device plus one, which now holds on two models and on a configuration nobody here wrote, so the spare entry is structural rather than an artefact, and section 265 leaves the shortcut button as the live explanation for it. And a part that looked like two entries per device holds nothing at all on the 300, which was written up as one model's accident and is wrong: section 265 above put a current configuration on the same remote and it holds two per device like the other model. The odd one out is the 2011 build date, not the model, and the three declared features offered as an explanation are withdrawn.
The remote also disagrees with its own USB descriptor about which model it is. It says one number in its own text and in its configuration, and the descriptor says another, and the two are Logitech's two regional versions of the same remote, one European. So a reader that asks the descriptor gets the region wrong, and the earlier reading that took it from there is corrected. The control is the other model, which has no regional twin and where the two sources agree.
Its configuration is a previous owner's, built in 2011, and that makes one earlier claim much stronger: every stored code is pointed at by exactly one key with no gaps, on a file nobody here authored. That was the last reading where the tidiness could have been an artefact of how these particular remotes were set up.
Removing one device from the Harmony 350 said which stored codes belong to which device, section 263. Danny took the Playstation off it and changed nothing else, so the two files differ by one device, where the pair before moved two things at once. Seven questions were written down before the read and six are yes. Exactly one set of stored codes empties, which gives the owner of every set on that remote, and the answer refutes what the earlier read had suggested: the devices are listed in the file in the opposite order to their code sets. That candidate had been fitted to a file where it could not fail. Two more parts of the configuration now have a measured meaning: one holds an entry per device, and it counts the Chromecast too, which has no infrared codes at all, so it counts devices rather than devices that can send something. Neither is placed on the slot map, because knowing what a part counts is not knowing which part it is, and that wants the firmware.
This library reads a Harmony 350's configuration off the remote now, and the read is four bytes short of a container, section 262. That family keeps its configuration in a named file rather than at an address, and the path was already on the list of things this library will open, so the read is four ordinary commands and none of them writes. The remote also states what it is, architecture 16 and skin 104, which had only ever been read out of a configuration file before. The four bytes are the container's closing marker: the remote reports the file's length as the configuration's own declared end, which stops just before the marker, and past that length it serves zeros. The marker is a constant for the family, so putting it back is a fix rather than a guess, and the result is byte for byte the copy concordance produced and passes all fifteen structure checks. So that remote no longer needs a third party tool to be read.
Then it was programmed, and its favourites named another slot. Four devices, one activity and five favourite channels, all chosen and written down before the read. Eight predictions, three wrong. The one worth keeping is that a real configuration is smaller than the factory one, 83840 bytes against 121251, because a factory file carries codes for equipment nobody owns. The strongest result is a prediction that failed: every stored code is still pointed at by exactly one key with no gaps, which was expected to break on a programmed remote and does not, so that is a property of this architecture rather than of an unexercised file. And the five channels are stored as five two step programs that load a number and hand it to the number sender, which is the mechanism already measured on the Harmony One; comparing the two reads shows exactly one slot going from empty to one entry, which is what names it. Eight of the fifteen slots are placed now. One apparent bug turned out not to be one, and the correction is recorded: a reader looked like it confused "this list is empty" with "I cannot read this", and measuring it showed a second reader three files away answers correctly, with the rule written in its own docstring. The conclusion had been drawn before anybody asked whether that reader existed.
What connects a pressed key to a stored code is exact on the Harmony 350, and its command names cannot be checked at all, section 261. The instruction that sends a code carries a group and an index into the infrared database, which section 33 read years of findings ago; on the Harmony 350 it closes completely, 130 send instructions naming 130 records with every record of every group named exactly once, which is a much stronger closure than a matching count would be. The attempt that produced it was to check section 260's names against Logitech's catalogue, and that route is shut: none of the Harmony 350's 130 codes decodes to a number, because all three of its device groups are the kind where a bit is which half of a beat carries the pulse, and each defeats a different one of our three readers. One of the three is identified with no catalogue at all, on thirteen lead in pulses matching a rhythm we already had; the other two match none of 681 rhythms and are the first unidentified families this project has found in a configuration rather than in Logitech's own data. So on that remote the archive is the only naming route, which inverts the usual relationship between the two sources, and its names stay unverified in the ordinary sense.
A configuration can name its own devices and commands, and two kinds of remote do, section 260.
Everywhere else here a command is a number with no name in the file, which is why identifying one
needed a 2.2 GB catalogue and two test accounts. The Harmony 300 and 350 carry a ZIP archive inside
the configuration, holding one file in which Logitech names every device on the remote and all 165 of
its commands, and the Harmony 890 and 895 carry a smaller one that has been in this corpus for weeks
unread. Each gives it a slot of its own and neither slot is a base slot, so one of the eight raw slots
sections 178 to 184 could place nothing on has a name now. The newer archive also states the record
layout of the log area, which is what named a second slot: an infrared event carrying a device, a
command and a time. The naming is not joined to the commands: there are more names than records, 61
against 52 on one device, and the device order is not the group order, so which record a named command
sends is open and is asserted to be open. metadataArchive in packages/codec/src/metadata.ts, no new
dependency.
Six of the Harmony 350's fifteen container slots are named, section 259. Its configuration has
been in the lab since 27 August and parsed since section 194, and nothing could be read out of it
because nobody knew which slot held what. Its own firmware answers: a routine that computes where a
slot's pointer sits, called from fourteen places each loading the slot number as a literal, which is
the instrument section 35 used on the Harmony 700. Named so far are the name tree, the architecture
record, the clock, the infrared database, the action list table and the parameter block; nine slots
stay unread and refuse rather than guessing. The firmware also hardcodes the marker offset at 0x47,
which is 0x0B + 4 * 15, so it is a third independent route to section 194's reading that the format
byte is the pointer count. With the infrared slot named, section 258's send count reads on a fifth
architecture: 106 of the Harmony 350's 130 codes state one and every one is 3. One container, so it
is a fifth architecture agreeing rather than a fifth measurement.
How many times a press sends a code is read, section 258. The ratio between an infrared record's two blocks: the first holds the code as many times as one press sends it, the second holds exactly one press's worth, so dividing removes what the protocol family puts in a press and leaves what the setting does. 1913 of 1913 records that name both blocks divide whole, one value per device on 60 of 62 device groups, and 3 on all six device groups of the two configurations Logitech compiled to our own specification, where the service states 3 for each of those three devices. That was the third of the three numbers FreeHarmony's device settings screen waits on, so all three have a value to show and what is open is writing one, which changes a block's length. Both halves of the answer had been in this repository since sections 127 and 228 and nobody had divided one by the other.
Our config write is missing three of a working implementation's steps, section 245. Read out of concordance on 3 September 2026, after two configurations had been written: a working write on a Harmony One is invalidate the flash, erase, write, verify, reset the remote, set its clock, and ours is erase, write, verify. The invalidate exists so that nothing references the configuration while it is being replaced, which matters most on the one architecture that executes its configuration in place, and the reset is the battery pull that has been done by hand after every write here. The transfer size is fixed already: both Logitech's client and concordance cap a write at 3150 bytes and ours was sending 32768, ten times longer than anything either has been seen to send.
Two of the three are implemented and both have been sent, sections 246 and 247. The invalidate was identified in the firmware before it was ever put on the wire, which is what stopped this project trusting its upstream name: it clears three descriptors in the remote's data memory and reaches no flash gate at all, so it is a cache drop rather than the flash operation the name promises. The reset is the escape section 97 had already read. A two block write on 3 September 2026 sent both, read back byte for byte, and the remote then left the USB bus, came back on its own running its application, and showed the ordinary activities screen: the screen asking for a sync and the battery pull are both gone.
And the control ran the same evening, section 248: it is the cache drop. The same container was written again with the invalidate sent and the restart withheld, and the remote showed the ordinary screen off the cable, with no battery pull. What makes that clean is that those exact bytes had gone onto that exact unit earlier in the day without the invalidate, and it asked to be synced until its battery came out, so the content, the target and the unit are all held fixed. The restart stays in the sequence because both working implementations send it, and it is now known not to be what closes the screen. The write was also a revert, so the remote is back where it started and the two writes together are the first edit-then-undo this project has done on real hardware. The third step, setting the clock over USB, is deliberately not implemented: our writer stamps the configuration and an arch 12 remote reseeds its clock from that stamp at every boot.
Phase 9 is done: a device out of Logitech's catalogue is on the spare Harmony One and the television answers it, section 242. The candidate below was written on 3 September 2026, 25 blocks read back identical, and Danny switched the LG on and off from the new row. The first attempt broke off halfway through its first block and the writer's compare had to learn what a half written block is before the rerun would go; the remote showed a status screen asking for a sync until a battery pull, which happens after a clean write too and so follows the write rather than the broken one, and which section 247 has since closed. The page's labels were wrong on the remote, measured through a stale font table and too long for their pads, and a second configuration with readable labels and a save stamp is on the remote since that afternoon: its clock is right, which is what the stamping rail exists for. That write took five attempts and three of them were the link rather than the code, section 242.
One of that afternoon's two puzzles is closed, section 243. A block of the spare's configuration region was found erased with no erase in any log, and the reading offered was the remote doing it to itself. The firmware says it cannot: the application reaches the external flash programmer's erase gate through exactly one wrapper with exactly one caller, the erase command's own handler, whose address arrives in a USB report, and the programming path it can use unasked only clears bits. So the erase was ours and what went missing was the record of it, which is why the writer now appends its own journal beside the configuration, one file per run.
The other puzzle is reworded rather than solved, section 244. What the remote shows after a write is not a claim to be unprogrammed: it is "Go to Website to update settings", one of thirty status screens the firmware ships, and the table keeps separate screens for an invalid configuration and a corrupted one. That distinction is one the remote really draws, since it showed the corrupted screen at the moment its flash was independently measured to be inconsistent and the website screen after a write that read back byte for byte.
And what selects the screen is read now, section 249. The firmware raises a status code, and the same number means the same screen on all three architectures read; the record it reaches is that code plus a base per container, which is why the message is entry 0 on a Harmony One and entry 5 on a Harmony 600. One routine displays a screen and it has exactly five callers, so there are five conditions and no more. The two configuration messages are the two arms of one test at the end of the routine that validates a container: it checks three markers in the file and then its checksum (on the Harmony 600 and 650 only while one setting is on, section 354), and a marker that does not match shows "go to the website" while a checksum that does not match shows "configuration corrupted". So the two messages mean two different things, precisely, and both of the screens seen on the bench are accounted for. The search that had failed was for a variable written with a spread of literals, and the index that matters is zero, written by a clear rather than by a literal. Reading it also confirmed the container's own pointer table from the firmware, since the second marker's offset is exactly where a table of 22 four byte items ends on a Harmony One and 20 on a Harmony 600.
And a second reading the same evening overturned what we thought the fix was, section 250. A status screen only appears if the remote's own verdict on its configuration has already been thrown away, and nothing in a write throws it away: the five places that touch that verdict are the boot, a licence check, the two arms of the checksum test, and the invalidate command. So a write on its own makes the remote check nothing, and the screen the bench saw came from a boot over a half-written configuration. What keeps it up is a latch: the remote only re-checks while it still holds a good verdict, so a failed check is a one way door and a power cycle is the only way back, which is the battery pull three earlier sections recorded without explaining. The invalidate's real job is therefore the opposite of suppressing the screen: it is what makes the remote read the bytes we just wrote and earn a fresh verdict. Section 248's attribution is withdrawn, and the run that settles it was performed on 4 September 2026: from a remote whose state was read out of its memory first, a two block write with neither command left the verdict, the re-check flag and the cached descriptors untouched to the byte, and the screen ordinary off the cable. Two erases and two block writes, and the remote's opinion of its configuration did not move at all.
The positive half ran the same day and it is watched now, section 251. The drop was sent on its own, with nothing erased and nothing written, and the remote's verdict went away. With the cable in it stayed away for six minutes across three reads. The moment the cable came out and went back it was restored, and the remote's own clock, which it loads from the configuration once per startup and nowhere else, showed it had been running for twenty three minutes without a break. So the remote re-checks the configuration we wrote, by itself, while running.
And the 525's own answer came with something better attached, section 254. What asks that remote to re-check its configuration is an instruction in the configuration itself, which no other remote here has: the other two decide to re-check on their own. Reading the interpreter around it also gave the address of the forty slot action queue on that architecture, and its three constants close on each other, so the forty that the writer's sequence rail rests on is now measured on two architectures with nothing in common.
And then an audit that found more of the same, section 257. Running the new check backwards over the afternoon found a second and larger re-derivation: a finding from 13 August already held the Harmony 525's whole instruction map, at the same addresses, including the one consequence I had presented as new. What genuinely came out of the afternoon is one table entry in the codec, upgraded from "we know where this handler is" to "we know what it does", which is the gap that finding explicitly left open. It moves no percentage, because no 525 configuration uses that instruction.
The audit also found a real error next door: a finding had presented one build's addresses as its whole architecture's. The Harmony 700 turns out to carry the same machinery with not one shared address, which was then read and asserted, so that claim now rests on two remotes instead of one and is stronger than it was.
And a correction with a lesson in it, section 256. Half of what those two sections read had already been read here on 9 August, on a different remote, and one of our own library constants carries it with the section number in its comment. So an afternoon went on re-deriving a claim the repository already held, and got it wrong on the way. The fix is one grep before reading a structure rather than after, and it is written into the tracing method: look up the constants the structure is built out of. What survives as new is the Harmony 525's own addresses, its map of sub-commands, and one of them consuming a second instruction's worth of bytes, which matters to anyone writing a disassembler for these lists.
The instruction's own layout, section 255, which corrected a claim section 254 had made an hour earlier: the number that selects the re-check sits in the instruction's operand rather than in its opcode, and an arch 9 instruction turns out to be laid out exactly as the format states, operand first and opcode last. What proves it is the interpreter comparing the third byte against an opcode the other two remotes already read. The way the bank was finally settled is worth repeating: find the last place the firmware sets it explicitly and check that nothing on the path changes it, rather than trusting either of our tools, both of which had guessed and one of which had guessed a plausible wrong answer.
And all three architectures were read, sections 252 and 253, so the trap is one remote's. The Harmony 600 has the same machinery and escapes for one reason, the Harmony 525 has different machinery and escapes for another, and only the Harmony One has the screen that stays up until the batteries come out. The 525 also picks between the same two messages by which container failed rather than by which check failed, so section 249's condition is scoped to the other two now.
And the Harmony 600 has the same machinery and not the same trap, section 252. Its poll, its armed flag and its validator sit at its own addresses and match the Harmony One instruction for instruction, and a connected Harmony 600 rests exactly where a connected Harmony One does, verdict standing and flag armed, which is measured. What differs is the one condition the trap rests on: a Harmony One arms its re-check only while it still believes its configuration, and a Harmony 600 arms whenever a configuration is merely present. So the screen that sticks until the batteries come out is a Harmony One fact rather than a Harmony fact. The experiment that would prove it needs a write command, and there is no arch 14 remote this project may write to.
Two things in section 250 are corrected by it. The re-check fires on the cable transition rather than while the cable is in, and the cable pin's polarity is the other way round, which came from naming a firmware routine rather than deriving what it answers. The latch, that section's real finding, is unaffected. And the restart the bench keeps seeing after a write belongs to the write: a Harmony One runs its configuration straight out of the flash an erase clears, and a drop with no write behind it does not restart anything. One read settles where the screen comes from: the identity block's software type says 0 running normally and 4 in safe mode, and the container these screens live in sits with the bootloader rather than with the configuration.
The missing page was composed first, section 241, and the candidate configuration for the first write that adds a device was in the lab within the hour: the spare Harmony One's own configuration plus an LG television with six commands, 1668321 bytes, seven devices, every one of its nine device list menus given a third page holding one row on a hit page derived from the full page's rectangles. Every reader accounts for every byte, the emitter reproduces it and the new page renders as Logitech's own short pages do. The write was 25 of the configuration's 26 blocks.
Phase 9 was blocked for two days and the block was worth more than the phase, section 239. Putting a device from Logitech's catalogue on to the spare Harmony One ran the composition against a second configuration for the first time, and two things it had been carrying as constants turned out to be per config. The state variable a device list row marks device mode with is one of eight different numbers across the fourteen configurations here, and it moves between two syncs of the same remote, so it is read off the file now; with the constant in place the spare's own configuration reported that it had no device list at all, which is what a hardcoded operand looks like when it is wrong. The second is the blocker and its first write up was wrong, corrected the same day after Danny said he had never seen the device list change shape: there is one layout, three rows to a page, and a page binds exactly its hit page's area count minus three. The spare drives six devices over two full pages, so a seventh needs a third page holding one row, which section 241 then composed. The wrong version compared a lead byte between two configurations, and a lead byte indexes that configuration's own hit map table. So the tool and the device were ready and one piece of phase 6 was missing.
A device that sends nothing was not counted, section 240, found because a count quoted in conversation did not match what Danny remembered of the remote. The inventory built its device list from the infrared groups, so a Wii whose device mode sends no code had no group and was not a device: nine reported where the remote's own device list draws ten. The device list is a route now, and it names devices the older routes could not reach, which moved the power on delay counts of section 236 from 127 to 129 pairs without a single delay changing, since a delay is found through the device's name.
The oldest unpriced rail has a number now, section 238. "Refuse an oversized sequence" had been
written down since 29 August with nothing behind it, because the only evidence was a lab note: a 25
step sequence hung a Harmony One three times out of three and nothing said what a writer should
refuse at. The number came off the firmware rather than off more runs at the remote. A Harmony
spools every action list into one ring of forty instructions, shared with key presses, state changes
and display events, and every push into a full ring is discarded with no error anywhere. So a
config can quietly do less than it says. Nothing in the corpus overflows: the sequence that hung the
remote peaks at 35 of the 40, and every configuration Logitech compiled from an ordinary account
peaks at 22 or below, so what the sequence is short of is headroom rather than slots of its own.
assertQueueFits refuses, and write-config.ts runs it before anything is erased. The hang itself
is still unexplained and one more reading of it is dead: the queue is a call stack, not an append
list, which is what a first pass through the same routines had it as.
And a configuration our own codec produced is now on a remote, section 237, which is the last box in front of adding a device. Everything written before today came off the remote it went back to; this one the codec emitted, every byte of it, and the remote handed it back identical. The change is one device's power on delay, six seconds to ten, which is the smallest edit the format admits.
What it cost to find out is the part worth carrying. A one byte edit is a two block write, because the checksum at the far end of the file moves with it, so two erases is the floor for any edit rather than a property of the one case that had been measured. A writer therefore needs a copy of a whole flash region and not of a configuration, since the checksum's block runs off the end of the file. And the verification read failed while the write succeeded, on a known transport hiccup, which is the worst way round to fail and is now fixed by reading through the retrying reader.
A remote's configuration has been changed, and the first change showed nothing, section 236. Two writes on the spare Harmony One, one byte of real content each inside a 64 KiB block reproduced from a verified dump, both read back and compared. The first raised a television's power on delay from five seconds to ten and the activity behaved exactly as before, which turned out to be what the firmware requires rather than a failure: the queue that carries commands tags every entry with a device and holds a command back only when an earlier entry names the same device, so a delay delays one thing, the next command to its own device. That television gets one command in that activity, so its ten seconds ran down in the background. The second write raised the receiver's, which does get a second command, and its gap grew from about six seconds to about ten on the bench. One reading, two opposite predictions, both observed. A third write then put all three changed bytes back, and the whole configuration read off the remote afterwards is byte identical to the dump taken before any of this, so the way back is a measured route rather than a plan.
Two things came with it. The mechanism is read on both bench architectures and the routine that carries it is identical on the two but for one literal, which is what lets a reading taken off a Harmony 700 image license a claim about the Harmony One that was written to. And the corpus says how often this bites: of 129 pairs of an activity and a device it switches on, 37 send that device nothing after the power code, so a quarter of the power on delays in these configurations can never be felt, with 13 containers holding both kinds at once. An interface that offers "power on delay" as a pause in the activity would be wrong about those.
A device's delays were not where the plan said, and the screen is what says whose they are,
section 234. The plan of record carried "which base slot 15 group holds a device's delays" as the
last reading before the first write that changes something, and no group does: that section's shape
is one per architecture over containers holding 0 to 7 devices, and its values are shared across
containers whose device counts differ, so it tracks the model. The delays are ordinary state
variables in base slot 13, eight per device, and the unit is a tenth of a second, which the
config states itself by drawing 451 labels from ( 0 sec ) to ( 45 sec ), contiguous, one per
position of the slider the remote's own menu offers. Logitech's service states the same inter device
field in milliseconds at exactly a hundred times the stored number.
Which device a delay belongs to needed a route of its own, because two vocabularies name a device in
one config and base slot 0 relates neither to the other: buttons and infrared groups go by an ASCII
label and delays go by Logitech's numeric device identifier. The screen relates them, on the page
that offers to put one device's delays back to their defaults: it draws the label in its title row and
its instructions copy that device's defaults into its current values. 19 of 19 devices over the four
containers that have delays, against 1 and 4 of 19 for the two orderings of the identifiers anybody
would guess, and Logitech's own button maps for the calibration account agree about which device is
which. So changing a delay is a same length edit of one u16, the cheapest change this format has.
And a Harmony One states the same delay, one commit later, section 235, which is what turns the reading above into something worth writing. Arch 14 was the odd one out: it keeps the number in a variable, and the Harmony 880, the Harmony 525 and the Harmony One keep it as one instruction at the top of the action list that switches the device on. That instruction had been read for weeks as "a quantity" with the unit unknown, and what settled it was Logitech compiling a configuration for the same three devices twice, once per architecture: the two agree device for device, so the operand is tenths of a second. 76 of the 83 devices in the lab now state a power on delay, the seven without one being the things nothing switches on. This matters because the only remote this project may write to is a Harmony One, and changing a delay there is one byte with nothing moving around it.
And Logitech's own account agrees about the numbers, the same section. Everything else that confirms a delay is read out of a configuration, so the check had to come from outside one, and it needed no write: their service states a power on delay per device in milliseconds. Against the configuration they compiled for the spare Harmony One it agrees on four of four devices at exactly a hundred times the stored number, and on two of those the owner has tuned the delay away from the catalogue default, with the configuration carrying the tuned value. Their record also states an inter device delay, and no instruction in that configuration carries it, so where the Harmony One keeps that one stays open on a measurement rather than on nobody having looked.
Two million of the archive's waveforms, rendered from Logitech's definitions, against our encoder, and nothing disagrees, sections 230 and 231. The infrared archive carries a rendered waveform for every command in their catalogue, produced by somebody else's code from Logitech's own protocol definitions, which makes it an answer key two million entries long that nobody here had a hand in. 1,894,306 of 1,894,309 first transmissions agree exactly, and 1,112,791 of 1,112,794 held repetitions, agreement meaning every interval identical rather than close. Nothing else that judges the infrared encoder is remotely that size: the corpus holds 3017 codes and the rhythms measured off Logitech's own compiler cover 35 families.
The return is seven defects it found, which is the point rather than the percentage. A frame's width
was taken from the family's name, and on 23 families the name states the total across the frames
rather than each, so Daewoo 16 Bit went out at twice its bit count on all 9492 of its commands. A
second segment states its own lead in and it need not be the frame's. A command's keycode states its own
cycles and they may not be the family's default: every RCAV1 24 Bit 2 command repeats the segment whose
lead in is 19800 microseconds where its definition's default repeats the one at 4000. How many frames a
code sends is the greater of what the definition names and what the cycles ask for, and taking it from the
definition alone made Revox 11 Bit send its first value twice and its second never, which is the
failure hardest to see from outside because the waveform is well formed and carries the wrong number.
A Pronto section cannot open on silence, and ours was keeping a leading space where the archive's renderer
drops it. And a pad shared across two copies is wrong wherever those copies carry different values.
The seventh is the one with a lesson in it. A cell may state the half that carries the bit first, and our table stored the pair the other way round and got away with it on 30 of the 37 families that do so, because their own lead in supplies the missing half. On seven it does not, and 1058 commands were going out wrong. Those seven were refused first, a code that would be wrong not being emitted, and the refusal was the right answer for the day it took to teach the frame emitter the other spelling. No measured row uses it, the old spelling being exact for all of them.
Nothing is left, and the last three commands were not what section 230 said they were, section 231.
Those three were attributed to values written in base four, on the strength of the family's name
containing the word Quad. Quad 5 Bit states two symbols and five bits and writes its values in
hexadecimal, so the base came from the name and the name was wrong.
The reading that closed it also took the table from 461 families to 600, which is the bigger half. 142 of Logitech's families spell a bit as a whole cell shape rather than as one of two lengths: a value is read a digit at a time and each digit picks a cell, out of four or out of sixteen. Base four and base sixteen looked like two problems and are one shape, so 142 read where 75 were planned, and the converter now answers for 599 of Logitech's 684 families. Every command that can be built agrees: 1,923,128 of 1,923,128 first transmissions and 1,135,097 of 1,135,097 held repetitions, over 428 families, up from 368. The controls did not move: the 34 of 35 rhythm calibration and the 29 of 29 block calibration both pass unaltered.
And that shape is read too, section 232. 84,694 commands of 29 families whose press cycle names two
infrared segments with different rhythms were refused because a table row held one rhythm: a family
can send several inside one press, and Classe 16 Bit Toggle sends four mode bits at a 442 microsecond
half cell, one bit at 880 and sixteen data bits back at 442. That is RC6's shape.
Then the whole population, section 233: 2,067,623 of 2,067,623 first transmissions agree and all 1,166,798 held repetitions, over 680 families, with nothing outstanding. The 240 commands not compared are the ones the archive renders no waveform for, so there is no comparison to make. Nine more readings did it, each one a refusal in section 232's census: the largest is that a two symbol family whose rhythm fits none of our specific shapes is a cell table of two, which states both intervals of a cell outright and so can hold a rhythm that will not split into a constant half and a carried one. The others are a press cycle's third block, sent when the key comes up; a code that states no repetition at all; a pad whose period is its own rather than the block's; the fourteen segment words a keycode may name; and a zero length interval keeping its side of the carrier.
Four defects of ours came out of it, three of them silent. Logitech's own field order is sequence then
token and reading it by token alone is ambiguous on 103 of their definitions, which put a repeat
cycle's value in the start block on 720 commands of one family. A segment's own width sits inside their
Payload and this reader had it one level up, so a docstring here said the field was always null. The
comparison test built its own waveform rather than calling the library's, and had drifted two
readings behind, which is the two-copies state this project's oldest rule forbids. And the table
generator dropped the new field, exactly as it dropped carriedFirst the day before.
make prontocheck is the run, about forty seconds, and it needs the public archive checkout and no
network. What is left is the keycode reader's closed set of segment words: 16,476 commands name a fourth,
Start on 15,146 and Finish on 10,442 among them.
And which remote is on the cable is read off the remote, section 226, which was the last of the
three things the write rails took a caller's word for. Two Harmony Ones enumerate identically, so the
question had been recorded as unanswerable; it is not, because the remote holds a 64 byte identity block
in its own program memory whose two GUIDs are exactly what Logitech's own service takes as a serial.
This project's first proposal was to fingerprint the unit by its configuration, and Danny refused it and
asked whether the vendor already had a way, which they do. The lesson is the ordering rule again:
look at how their software does it before working out how it should be done. The trap worth knowing is
that the field actually named the serial is 0xEE on every remote read here, so comparing it matches
every unit against every other and says yes with confidence. The values live in the private lab and
FreeHarmony will keep them with the user's own data; the contribution probe still emits none, because
its report is published by other people. Exercised on the spare the same day: it identifies the
unit, the rehearsal's dry run matches it against the recorded value, and both refusals were shown to
bite, a record changed by one character and a record missing altogether. All reads, nothing written.
The compatibility gate is performed rather than asserted, section 225, which is the one write
rail with a specific job: refusing a configuration built for a different remote. It took a boolean and
every caller passed true, so the check with the most to say was the caller's opinion. It takes the two
inputs now, what the configuration states and the version block the remote sent, and the rail compares
them, over a mapping that had to be derived: PROTOCOL carries the architecture, and the byte this
project once called the protocol is platform, which is the same on arch 12 (Harmony One) and arch 14
(Harmony 600 and 700), so reading it as that would have accepted a Harmony 600's configuration for a
Harmony One. Fifty comparisons over the corpus, none disagreeing, against four remotes whose values
concordance read independently, with a control of forty configuration and remote combinations of which
32 must refuse. What it says about the first changing write is the useful half: a configuration read
off a remote carries no header, so it states none of the six fields, and the gate has something to
compare only once a configuration we produced is being written.
And the rails had a third bypass of one class, section 224, found by performing job 2 of the write
review: the guard on the transport was right and the permission was public, a method on the very
object openHarmony hands back, so two lines erased firmware with writing disabled. Three fixes in a
row asserted a predicate over exported names and twice what reached the device was not an exported
name, so the assertion now enumerates the object's whole surface instead.
And the layer that names a command is read, section 220. A configuration addresses infrared codes by number and says nowhere which one is volume up; the platform holds that separately, as a map of named commands per device and per activity. Two things came out of it. The vendor's schema says a device's map and an activity's map are the same shape differing only in what they hang off, which is this project's own operating concept arriving by a route with nothing in common with the fifteen configuration measurement that produced it. And the platform separates a canonical button vocabulary, held independently of how a command reaches the device, from a device's own commands, which exist only as an infrared code. That split held with no exception over the 1191 commands captured from two test accounts on 30 August 2026, and the named half is published as vocabulary with its sampling stated: those are working test accounts whose contents change, so the list is a floor rather than the platform's.
And what it can be asked is in there too, section 219: 298 operations over 19 service interfaces, each with its parameters and the type of its answer, which is what an importer is a sequence of. Three sources describe that surface and none contains another, Logitech's two clients and the live service's own listing, so the platform is bigger than any single count of it. The sharpest thing it bought is a negative: exactly one operation can hand back the vendor's own infrared protocol definition, the thing sections 159 to 171 measured the hard way, and that operation is broken on the live service, reproducibly on two accounts, while two neighbours on the same address answer normally. So that avenue is closed rather than unexplored.
The vendor platform's own data model is in this repository, section 218 and decision 14, which is
the first time this project has had the schema behind the bytes rather than names inferred from them.
1352 types, of which 470 are the service's contracts, with 366 references between them and 1291 enum
values. It is recovered from the client's generated service proxy, so the contracts in it are the
schema the server declared rather than a client's internal model, and it is checked against replies the
live service actually sent: on Account, Activity, Device and Remote every field in the schema appears in
a live reply, 21 of 21, 25 of 25 and 32 of 32 twice. The check also runs the other way and that is a
finding of its own, the service is ahead of the client build, returning ten fields on a device that
the proxy has never heard of, three of which name the delays docs/predictions-sequence-delay.md
predicted from a config. docs/myharmony/model.md is the reading and it is to be consulted before
naming a field. What crossed is schema only, asserted rather than assumed, so no reply, account or
identifier came with it.
The screen's text reads back, section 112, which is what the application needed before it could
show a config's activities: their names are drawn by a mode page's screen program and nothing else
names them. A glyph code is not a character and not an encoding: it indexes the config's own font
table and is assigned per config, in the order characters first appear in the generator's string list,
so two configs of one remote disagree about code 20. What is stable is the typeface, so a code is
resolved from its glyph's pixels against a hand read alphabet, seven of which cover the corpus.
170920 of 170922 drawn glyphs come back; make text. The
seeds and the method for an eighth typeface are in packages/codec/bin/alphabets.ts.
A code is one character and a character is one code, section 124, and that rule is the check to
reach for before trusting a seed: it is the generator's own, since a code is a character's position in
the string list it walks. Three hand read labels were wrong and each showed up as a character sitting on
two codes at once, 9 read as 8 on arch 9, a lowercase z read as Z on arch 14, and an I read as
l on arch 12. Every one of them was drawn in a single word in its own container, which is why the
proof string each seed carries could not catch any of them, and every one was caught by a second
container of the same skin. The rule also resolves what no shape can, I against l, in place of a
fallback that assumed two configs of one skin number their codes alike. Adding a gap filling source
labels a shape and not just a code, so when two characters share a shape both codes have to be named
or the shape is claimed for one of them.
Which key starts which activity is read, section 120, and which drawn name it carries is read on
all four architectures, sections 121 and 125. The chain is four hops, because nothing in the format names
an activity: a mode page's tagged list binds a key to 0x7F, that base slot 10 list carries 0x1F with
operand 0xFF | set selecting a base slot 9 entry, that entry's list writes CurrentActivityState with
0x80 | n. Eleven of eleven containers, four architectures. Every binding is a press, every activity is
reachable, and all of an activity's keys are on one page, which is what makes "the page that names
this activity" mean something. The structural closure is that an activity page's 0x7F operands are a
contiguous ascending run of base slot 10 indices, 16 of 16 activity pages against 373 of 1152 pages
generally that are not.
The idle value is base slot 13's first, the field section 60 marked unconfirmed, and it is exactly
the value no binding writes. one_config is what makes that a finding rather than arithmetic: first is
7 where the highest is 8 and 8 is bound to a key. So section 86's "value 0 is no activity running"
was the wrong reason for a right count, corrected in section 120.
The name comes from the modes the chain enters, not from geometry: an activity's lists also carry
0x7E, and the mode they enter draws the activity's own name, so the page's string that relates to one
of those is its label. That is how three architectures do it: arch 8 22 of 22, arch 9 4 of 4, arch 14 13
of 13, and with arch 12's own route below, 50 of
50 activities, make activities. Four rules make it a function and each was found by having it fail: an exact match beats a
contained one, a per mode chrome test, one label to one activity, and a second pass for a label the menu
wrapped onto another row. The exact match rule is the one to remember, section 124: an activity's
chain enters the mode that lists the devices, so every activity says every device's name, and reading
containment as sufficient let one label be claimed by all four activities of a Harmony 880 and then
dropped from all four as chrome. The number was 23 of 35 for a day, and three of those 23 were fragments
of a wrapped label, two of them belonging to a different activity than the one they were reported for.
Arch 12 does not use any of that, and it is the better route, section 125. No string rule can work on
a touch panel: one_config's three activity pages bind scans {50,51,52}, {50,48,49} and {48,49} while all
three draw labels on the same rows, so no fixed code to row map can exist. What a One needs is base slot
17's hit map, and the missing link was ModePage.lead, the arch 12 only byte section 66 read and
nobody explained: it is a zero based index into that map, so the rectangle a key covers is stated and
the label is the text the firmware's own hit test puts inside it. 11 of 11, and it runs before the string
matching, because a stated answer beats an inferred one. The closure is a demand the container makes on
itself, that a page only binds codes its own hit page offers, 268 of 268 and 104 of 104 where every shift
breaks 54 to 227. packages/codec/src/touch.ts also carries the panel to pixel transform, whose y
half is arithmetic (872 panel units and 54 pixels are one row measured twice) and whose x half rests on
one reading and is marked as such, though no name depends on it. Under it the panel is three blocks at
pixel rows 33, 87 and 141, one or two across and never three, plus a bar from 191 to 253 that runs off a
220 pixel display: which is exactly the unprompted description of the remote itself, two touch points below the
screen and a key at each side, so 48 to 53 are the blocks, 43 and 44 the points and 46 and 47 the keys.
Which code lands where is per page, in the order the rectangles are stored, so section 121's proof
holds for the codes too.
Every key a screen labels now carries that label, section 128, which is what turns the button table
from group 3 #29 into a word. Two populations first: a scan bound by a mode page is a key the screen
speaks for and a scan bound by a base slot 9 set is a key on the keypad, and the two are disjoint,
sharing no code at all on arch 9 (Harmony 525), arch 12 (Harmony One) and arch 14 (Harmony 600 and 700)
and exactly one on arch 8 (Harmony 880). Then the place: on a One base slot 17 states the rectangle, so the
label is the text inside it, attributed to the nearest region rather than the firmware's own first
match, which is right for a touch and wrong for a label since a long right hand string starts inside the
left hand rectangle. Elsewhere the keys are two columns beside the screen and the rows are measured
from where the activities section 121 names without geometry are drawn: four rows on arch 8, two on arch 9
and two on arch 14, with the left of each pair settled per architecture and not assumed. 98.9% of 6989
screen key bindings, and 3100 of the 3106 that send a code.
The rule that suggested itself fits the counts and is wrong, and it is the lesson of the section: the k-th key in ascending scan order taking the k-th row of text pairs four keys with four rows on the 600's own activity menu and gets two of them wrong, because two keys share a row and the outer rows are chrome. A key belongs to a place. Two closures hold the reading up, one of which reads no text at all: every two item row in the corpus has its two keys on different action lists, with no exception, and the labels agree with the activity chain on 62 of 63 keys, the exception being a "1 OF 2" page indicator drawn in the bottom row's continuation slot and left in rather than special cased.
A config's screens can be drawn now, section 129, and the bench shows one beside the keys it
binds, made out of the bytes per request. That is the shape FreeHarmony needs, since an editor has to
show what a screen will look like after a change and must not carry a second implementation to do it.
It is packages/codec/src/render.ts and make render, with the PNG encoding in src/png.ts because
the bench serves the same rasters over HTTP and two encoders would be two things to keep right.
It is here rather than in FreeHarmony because it is also the check that fails differently from every other
test in this repository: a reader test says a number came back and cannot see a label half a row out, an
icon over its own caption or a colour channel one bit wrong. Every mode page of every container
renders with nothing unresolved, over 1500 pages on four architectures, which needs a picture's
extent, a glyph's encoding, a font set's first code, a referenced string's address and a page's program
pointer all to be right at once. And that claim was hollow for a month, section 148: it counts
pictures the renderer looked for and could not decode, so an instruction the renderer never looks at
contributes nothing to it. The renderer knew screen opcode 2 and not opcode 3, and on arch 9 (Harmony
525) every picture is an opcode 3, so a rendered Harmony 525 page drew its text and left 4549 of 6144
pixels untouched while the check reported nothing missing. Same defect as section 103's catch-all
owner: a claim whose falsifier is outside its own population. The test that can fail counts the naming
instructions by walking the programs and compares that with the renderer's own tally, per
architecture, because the whole shape of the mistake was one architecture at zero while a total
looked healthy. Three things it needed that no reader did: the display size, which the
configs state through their own full screen pictures, both picture opcodes saying it and agreeing
exactly on nine containers, which is what took arch 9 (Harmony 525) from having no witness for its
96 by 64 to having the only one; the pen advance, which is nothing because the
gap between letters is a column the glyph carries; and the pixel byte order, where the first reading was
wrong. A pixel is big endian RGB565, the only field here that is not little endian, because it is
stored the way a display controller is fed rather than the way the container is written. Little endian
drew a Harmony One's buttons as rainbow stripes, and the test that pins it says out loud that most
pictures cannot tell the two apart, since a black and white picture reads the same either way.
A page is a set of screens, not one, and renderVariants walks the arms: a screen program switches
on a state variable, so each appearance carries the condition that selects it, named through base slot 0
where the variable has a name. The bench offers them as buttons. What that immediately produced is
section 130, because it made the question "which variable is this" unavoidable: base slot 13's first
seven records are the firmware's clock, first being the value a variable holds when the config is
generated, and all seven equal the corresponding field of base slot 3's build timestamp in all 21
containers. Section 74 had read three of them as a date from the action list language alone, and the
weekday counts as base slot 3's does, from Sunday, section 322, where this said a Saturday. That also generalises section 120's idle value:
it is the generated value, and for CurrentActivityState the two coincide because nothing is running
when a config is compiled.
Two thirds of a config's drawn text had never been read, section 121, which is what fell out on the
way. Screen opcode 4 draws the glyph string at a u24, and in 12052 of 12052 instances that address is
the payload of an opcode 5 instruction in another program, so a string is stored once inline and
referenced everywhere else. make text went from 65456 glyphs to 146846 on the day, and stands at
170922 now that two more configs are in its population, with every sample still
reading at 100.0%. Nobody had followed the pointer because the byte accounting never
complained: the bytes were already claimed by the program holding them, and a comment in screen.ts
said opcode 2 was the only instruction naming a place outside its own program. A shared string is a
writer rail: editing one in place changes every draw that names it.
Step 8, the contribution probe, exists. Step 6's action list language is read, section 73:
both dispatchers, every branch, to the RETURN. All twenty base slots were already labelled, so
what is left of step 6 is small and it is measured rather than estimated.
The number now carries a depth, and that distinction is the point. Knowing which routine an
opcode reaches is not knowing what it means for a config, and counting the first as the second
reported 100% for a language a tenth of which nobody can name. packages/codec/src/actions.ts is
the table, reading gives one instruction's, readingCoverage gives a config's:
| share of 86947 instructions | |
|---|---|
| meaning | 98.6% |
| placement only | 1.4% |
| no reading at all | 0 instructions, nothing left anywhere in the corpus |
The population is 58 smaller than it was, and that is a correction,
section 139: 0x3F with a high byte in 0xD0 to 0xDF is a six byte instruction and the slot after
it is its argument, which the table had been resolving as an instruction of its own at depth meaning
every time. Section 73 wrote that consequence down and nothing acted on it for a month. takesFollowingSlot
is the predicate; Container.actionList still returns the slot, because the emitter reproduces it.
Against 24.5% with no reading before sections 70 to 74. Per architecture: 98.6%
on arch 14, 98.8% on arch 12, 98.6% on arch 8 and
95.9% on arch 9. Every figure here is recomputed, make reading, and
that is new: the table used to quote 97537 instructions and 97.9% and nothing checked either, so when
section 103 moved the number for the first time it turned out that no sample list reproduces 97537 at
all. The population is defined in packages/codec/bin/reading.ts and nowhere else now.
The unread column is empty and the state is unreachable, sections 107 and 108: 0x6E was the
last opcode in it, six instructions, and it is a modulo, and section 108 read the last three opcodes
that had a handler and no reading, 0x65, 0x66 and 0x76. An action list can make a remote write
to its own external flash, which is what those first two do, and the region they write to is one the
firmware allocates itself rather than the one base slot 2 declares. What is left is all placement and mostly one thing, 0x3F band 0xC0 on arch 12, and
it is hardware state rather than config structure. Section 102 read it and it stayed placement;
section 103 read the state machine behind selector 17 and it did not, which is 68 of the band's
106 uses per config. The band is three
fields, { bit 0; bits 1 to 3; bits 4 to 8 }, and three mechanisms: selector 17 sets the display's
light level, from four levels, three thresholds and a fade rate that base slot 15 states; selector 16
enables an I2C device at address 0x60 through LATC bit 5; and 0 to 12 set that device's thirteen
channels from a two bit table in base slot 15's twelve spare bytes. Which device it is is not
established, section 106, and the firmware never switches it on: only a config does. Two closures: the corpus uses exactly the fifteen
selector values the handler accepts out of thirty two, and the light level is an index into the 27
distinct CVREF voltages the part can produce, a table derivable from the datasheet. Do not expect
what is left to move by comparing configs: the band's uses are identical in both One configs despite
one having five devices and eight activities and the other one and one.
The two biggest items turned out to be things the remote does, not things a config describes.
0x75 is the beeper, four tones from 461 Hz to 4.7 kHz, gated by 0x3F high byte 0xF3; and
0x07 high byte 0xF8 steps a date held in state variables 3, 5 and 6, which are therefore
firmware defined and must not be reused. Sections 73 and 74.
Read a dispatcher, not one handler at a time, and count who uses an opcode before choosing
which firmware to open. The second rule is new and it cost three misreadings in one section:
0x73 and two 0x3F bands were all read on arch 14 and all used only elsewhere. One query says
which image to open.
Above 0x65 the opcode is the instruction and the binary search at 0x0EC8E names a handler for
each; 0x80 | n is one instruction with a seven bit field, a write into state variable n. Below
0x65 the operand carries the rest of the opcode, in bands: 0x1F is a register machine, 0x07
thirteen operations with no argument, 0x0F peripherals and diagnostics, 0x3F four bands one of
which is a six byte instruction. 0x3F's bands are the only structure in the format that is not
one table across architectures, so they must not be ported. Nor may 0x0F's, section 139.
Below 0x65 the dispatcher tests ranges rather than those four values, section 108, so 0x20
behaves exactly like 0x1F; the corpus only ever emits the canonical four, which is why reading it as
four exact cases never showed up in a number. Three structures are not one table, sections 107 and
139: 0x3F's bands, the whole opcode block 0x65 to 0x6E, which only arch 14 implements, and
0x0F's bands, whose table was read from arch 12 (Harmony One) and arch 14 (Harmony 600 and 700) and
answered for arch 9 (Harmony 525) too, calling a call a no-op twelve times per config. Arch 9 and arch 12 test each of those ten opcodes in
the same ladder and branch to the dispatcher's exit, and their configs never emit one. So the shift,
the boolean operations, the device record writer and the modulo are arch 14's alone, while the
multiply and divide just above them, 0x78 and 0x77, are everyone's. 0x6F belongs to nobody: it
tests the accumulator and returns from both arms, on all three architectures we hold firmware for.
The byte accounting has no architecture sized remainder left. It used to name two: 5437 bytes
in the arch 12 safe mode container, which was one font set the reader had cut to a single glyph,
section 78; and 25819 on arch 9, which was infrared class 5 and is section 82. Section 83 then took
the six shapes that were left in every container down to three: base slot 0's frame is length + 2
because the terminator sits outside the field, an empty counted array is still an array, and the 4
or 34 bytes above base slot 7's table are base slot 8's leading action list, which also turned
up that every mode page's list is inside base slot 8's section. Section 84 read the last three and
two more: a screen program carries a SCREEN_END even where a jump means nothing reaches it, which
was the whole arch 8 family of 49 to 64 single zero bytes; base slot 3's section is three bytes
longer than the clock record and base slot 17's is two where it names the picture bank; the key
table's extent is its mode record's, and an empty record is the wide form; and twelve arch 12
bytes belong to base slot 15 and to no group, by position rather than by reading. Those twelve are
read now, section 103: group 9 continuing past the six entries its header declares, four bytes as
one more pair of device levels and eight as a table of two bit fields, with no remainder. And they
are claimed by reading now too, which took a year longer than reading them: the accounting kept a
slot-15-spare owner filling whatever was unclaimed between the lowest group and the pointer array,
so zeroing any group's entry count let the catch-all absorb what the group stopped claiming and the
report still said 100.00% with no gap and no overlap, over 32 bytes on a Harmony One and 28 on a
Harmony 600 and a Harmony 880. A claim made because a run was left over cannot fail, which is the
same defect as an unfalsifiable test and it sat inside the number that measures M2.
Every user config is accounted for to the byte, sections 66, 67, 75, 82, 83 and 84, with zero
overlaps in all nineteen containers. Not 100.0% to one decimal, which it reached a section earlier:
nothing unattributed at all, in eighteen of the nineteen containers. The last
structure was a pool of tagged lists packed end to end, one per mode page plus one per base slot 9
set, bounded below by a mode entry's end and above by the lowest address another reader names.
That completes the first two of milestone M2's three parts on every architecture. The exception is
h525_safemode_ahcm, the arch 9 safe mode container, at 98.2% after section 85, which corrected
two arch 9 readers that every other container agreed with: opcode 22 takes one operand and not
eleven, so the picture belongs to the opcode 3 after it, and a monochrome picture row is padded to a
whole byte. Both were invisible until a container turned up with an odd width and four instructions
in an order the corpus had never carried. Its last 283 bytes are four runs nothing points at, named
in section 85 and deliberately unclaimed.
The third part exists and round trips, packages/codec/src/emit.ts, make emit. rebuilds is
the mirror of claims, owner name for owner name, and every owner the accounting claims is
rebuilt; the bytes come back identical on all nineteen containers and the residue copy writes
nothing at all on eighteen of them, since every byte is now written by a rebuilder. It builds into
a buffer
filled with 0xA5 rather than into a copy of its input,
because an emitter that starts from a copy passes a round trip test while writing nothing at
all, so the tests that carry weight are the negatives.
The number has a depth, the same way actions.ts does. framed bytes come from typed fields,
5.5% to 38.3% depending on the sample; carried bytes came out of a reader as an opaque run, and
that is nearly all of a config. This said that was because a glyph and an encoded picture could not be
re-encoded from their pixels, since several streams draw the same image; section 363 found Logitech's compiler
always writes the same greedy stream, and every picture and glyph of the 37 containers measured on arch 8, 10,
12 and 14 comes back byte for byte from its pixels, so the bytes stay carried because framing them buys nothing
for reading, not
because it cannot be done. Do not treat moving those bytes as the obvious next job: what a picture
means is already read, so framing the body would move the number 60 to 80 points without anything
becoming clearer. What it would buy is the ability to change an image rather than reproduce
one, which is a product question. docs/plans/002-the-roadmap.md, milestone M2.
Base slot 0 is read, section 77, and it was the emitter that found it worth reading: it was the
one section whose bytes the accounting counted while nothing inside it had ever been named, because
its 0xFEED frame states its own length. It is a list of 0xA7 framed nodes, u8 tag; u16 4 + len(name); u16 level; u16 index; char name[], and level 1 names base slot 13's state variables,
entry by entry. What opened it was the arch 9 safe mode container, whose first node is not called
Root: FRAME_PROLOGUE was never a prologue, it was the first node, and two of its nine bytes were
that node's own length.
Every device in the corpus has its name, section 126, 63 of
63 in 15 user configs, and the route is ASCII rather than pixels. Base slot 0
names no devices: a device's label is a prefix of a state variable's name, <label>_<property>_<values>,
where a name belonging to the config has a number in the property's place instead, which is the
discriminator. What ties a label to an infrared group is base slot 13: the variable's transitions carry
one action list instruction, and for a Power or Input variable that list is the one that sends the
code, so 0x7D's own operand names the device. 102 variables reach exactly one group and none reaches
two. Behind that, elimination for 5 and a mode's drawn title for 3, in that order because the title is
the label on arch 9 and arch 14 and a command name on arch 8 and arch 12. Two closures: the ASCII label
is also drawn, 53 of 55 exactly, which is two readers with no shared code agreeing; and on arch 9 and
14 shifting the pairing to the next group breaks 16 of 16. make devices, and the column to watch is the
source rather than the total.
The shared walk from a list to the groups it sends to must not be memoised, section 126, and only
arch 14 could show it: a nested walk stops at whatever the outer one had visited, so caching it lets a
list inherit a truncated answer. Arch 8, 9 and 12 carry 0x7D directly and passed; arch 14 emits
{0x7F, 0x7D, 0x7C} with the send one list down, and every arch 14 device lost its name at once, 63 to
47.
And 0x7D answers two more questions the application asks, section 126: what a button sends,
3106 bindings across the corpus and every one of them a press, 85 of those macros of several codes in
an order that matters; and which devices an activity drives, the groups its base slot 9 set
addresses, one to three per activity. inventory composes devices, activities, the build timestamp and
the idle value into one object, which exists so that FreeHarmony does not assemble it and become the
second copy.
A config states its devices and its activities, section 86, which is what the application needs
before it can show anything. A level 1 name is <label>_<qualifier>_<values> and values is its
variable's highest value plus one, 250 of 250, which settles the field section 60 could not explain.
Every container with a name tree names exactly one CurrentActivityState, whose highest value is
the number of activities, and a device is an infrared group. The calibration is section 58's
deliberate pair: a config Logitech compiled for one device and one activity reports one and one, and
the arch 9 safe mode container reports zero activities. The record's eight byte values are
transitions, u8 zero; i16 from; i16 to; { u16 operand; u8 opcode }, and the instruction is an
action list one. packages/codec/src/inventory.ts is the application's view of it.
The names are the user's own equipment, so no brand out of a contributor's config is quoted, in a document or in a test: counts and shapes. The generic role words the generator emits are structure and appear freely, and the one brand in the repository is from our own sync, section 58.
What the pool holds is settled too, section 69: each non slot 9 list is a second copy of one
mode page's own list, the k-th copy belonging to the k-th page in mode table order, identical in
meaning except that opcode 0x7F's operand names a different base slot 10 entry holding an
identical action list. Nothing reads a copy, and an emitter must still reproduce it. Section 68 got
this wrong twice by pairing the runs by address rather than by mode table order and by comparing
them byte for byte.
Arch 8 closed on 8 August 2026 and needed no firmware to do it, section 75. Its whole
remainder came from one byte: an infrared record header is 12 + 9 * count with the count at
+0x0B, not the flat 21 bytes section 61 read, and 37 records of that contributor's four configs
carry a second pointer group. That one number explained three separate gap families at once, 37 short
headers, 37 unclaimed blocks and the 37 gaps between them, and none of the counts moves when the
config grows from 234 records to 462.
What that second group holds is read now, and the 37 was never a property of arch 8, section 134.
It is the same code with one biphase bit cell inverted, a mark and a space of equal duration
exchanged at a fixed offset, in every block pair of every one of the 37; the records read as RC6 mode 6
at 36200 Hz, they all sit in one device group, and the two arch 8 configs contributed later have
zero. So the count follows the equipment. Two things to carry from it. The test asserting the
count was called every arch 8 config has exactly 37 two group records and its body
looped over four configs of one contributor, so its title was false while it passed, which is
section 75's own falsifier met and unnoticed. And it was found by a decoder declining to read
those records rather than by looking for it, which is the second time a rule survived because the
corpus agreed with itself.
Read the whole gap list before choosing a target, and this is the second finding it produced:
make coverage --detail used to print only the largest few of 128, and both this and section 66
came from asking for all of them and noticing families with the same count. It prints the
families now, length times count sorted by total bytes, computed over every gap rather than the
listed ones, so the next one of these does not need the hand count.
Arch 9's class 5 infrared is read, section 82, out of the 525's own firmware: h525_code is its
whole internal program flash, loading at 0x0000 with the application from 0x1000, and its SPI
primitive at 0x07F8E is arch 9's single config read choke point, the analogue of arch 14's
0x1B9AC. Class 5 turned out to be class 1 with a dictionary: a header pointer names a body of
one byte indices, the body names a symbol table, and the table names short pulse blocks that every
code with that pulse pair reuses. One body expands to a textbook NEC frame, repeat header included,
which is the closure. Every field width is a literal in the firmware that reads it.
Disassemble it with --part 4550. The 525 is a PIC18F4550 and the default register map is the
67J50 family's: 65 of 139 addresses disagree, the whole CCP block moves, and the infrared carrier
setup reads as a duty cycle write instead of a PWM mode write. The wrong map produces a listing
that is readable and wrong, which is the failure this project has recorded twice before.
Its safe mode config was the next piece of work and it was bigger than it looked. Found at
flash 0x818000, it parses, its checksum recomputes, and it contradicted six claims the corpus
asserts. All six are re-derived and not one of them was a fix, sections 77 to 79. Four became
findings: base slot 0 is a list of named nodes, and a font set's second header byte is the first
glyph code with the count not keyed on the architecture, which took the arch 12 safe mode
container from 39.1% attributed to 99.6%. Two dissolved on measurement: base slot 1's extent is the
gap to the next pointer like every section's, and the log area's range obeys every rule section 47
states once it is not measured against a chip size taken from the same field.
It is in the corpus now, h525_safemode_ahcm, and in the corpus wide claim lists rather than
excluded from them, where it is the counterexample two of them name: its font sets start at code 32
and declare four counts. Excluding it would have left the corpus agreeing with itself, which is
the condition that hid the first glyph code, and section 85 is the same lesson twice more: it holds
the only picture whose width is not a multiple of eight and the only opcode 22 that is not followed
by an opcode 3, and each of those broke a rule every other container had confirmed. Three arch 12 assumptions came out of packages/usb
on the way: the version reply was matched as a whole byte, its length was fixed at twelve, and the
region validator was hard coded. docs/memory-map-525.md holds the predictions against the
measurements.