Marty reported banner impressions never moving while text ads did. They were
serving the whole time — we were reading the counter backwards.
Measured against the live network, because none of this is documented:
TEXT `remaining` counts DOWN from the purchase to zero.
ad 2689: 1,111 left mid-flight, 0 once complete.
BANNER `remaining` counts UP as impressions deliver, straight past the
amount bought. ad 2707: 3,521 one day, 6,129 the next, on a 2,500
buy. Every live banner sits above its purchase and rising.
Reading both as count-down made every banner report "0 served" while quietly
delivering thousands — and made stop() treat delivered impressions as
unserved, so stopping a finished banner refunded the lot. That was giving
back inventory that had already been spent on the network.
interpret(kind, bought, stat) now resolves both directions in one place, used
by stop() and by the table. served + left always reconciles to what was
bought and neither can exceed it.
This also supersedes the earlier "counter drift" note: the drift WAS the
count-up, seen through a count-down lens.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Marty spotted rows where "left" exceeded "bought" — 3,521 remaining on a
2,500-impression purchase, with "served" reading 0 across those rows.
Reproduced against the network: on 11 of 26 campaigns the ad app's own
assigned/remaining counters have grown past what we inserted. We write
assigned = remaining = the purchased amount; later both sit higher. Actively
serving campaigns still reconcile exactly (served + remaining = bought), so
our arithmetic is right and the inputs are drifting. The cause is inside the
ionCube-encoded ad application, so there is nothing to fix at source.
Direction matters: this is OVER-delivery. Members receive more impressions
than their allowance and the ledger still only charges what they asked for,
so nobody loses anything.
The table now clamps both columns to what was actually bought, so a row
always reconciles: served + left = bought. Showing a member more impressions
remaining than they purchased reads as broken software, which costs more
trust than the bonus impressions are worth.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Text placements out-perform banners on this network — 357 text ads average
~171 clicks each against ~150 for banners, on far less inventory — so
members can now run them too.
The format is measured, not guessed. Across 60 live text ads: Subject caps
at 20 characters and Body is ALWAYS exactly three <br>-joined lines of at
most 24 characters. Every ad in the sample obeyed it.
Emoji: the NAS columns are latin1, but so is the connection, so UTF-8 bytes
pass through and render — which is how the existing emoji ads got there. The
bridge's clean() would have deleted every emoji via iconv //TRANSLIT//IGNORE,
so text copy now uses clean_ad_text(), which preserves them and strips angle
brackets instead. The bridge builds the <br> separators itself from a lines[]
array, so a member can never inject markup.
The engine writes plain text to a tight budget and we apply emoji ourselves:
models cannot count characters, least of all with emoji, so decoration is
arithmetic we control. It also lets the emoji rotate independently of the
copy. The palette carries no money emoji — the network is full of them, but
implying earnings outranks matching the neighbours.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three fixes from live testing of the Traffic Desk:
- /p/<id> with no page built now 302s to /join/<id> instead of returning
raw JSON 404. Any /p/ link already on a banner, flyer, or in a DM must
always land somewhere useful.
- The personal-page ad destination is only offered (client) and only
accepted (server) once a page actually exists; otherwise the member is
pointed at the Page Builder.
- Members can stop a running banner and get the unserved impressions back
in their monthly balance. Counters are read before deactivation, since
deactivating zeroes `remaining` and would look fully served.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>