==0ct0 Inc.==
Volume Zero, Issue 1, Phile 8.1 of ?
M5PORKCHOP v0.1.8b-PSTH - POST-TRAUMATIC
SLAB HEAP DISORDER (PRE-RELEASE)
"the pig fought the heap. both lost. both came back."
========================================================================
--[ 0 - TL;DR
+--------------------------------------------------------------------+
| |
| THE PIG HAD A NERVOUS BREAKDOWN. IN RAM. ON CAMERA. |
| |
| WE REBUILT THE HEAP SUBSYSTEM FROM THE GROUND UP. |
| |
| - Reservation fence replaces 5-phase boot conditioning. |
| 80KB fence pushes WiFi driver buffers high. Fence freed. |
| Deterministic. 3 lines. No more alloc/free ballet. |
| |
| - Graduated pressure levels (Normal/Caution/Warning/Critical) |
| with hysteresis, asymmetric escalation, and pressure gates. |
| |
| - Adaptive conditioning cooldown: 15-60s based on heap state. |
| Old 5s minimum caused a death spiral. |
| Empiricism: 1, Napkins: 0. |
| |
| - Knuth's 50% Rule confirmed on our heap. On-demand only. |
| |
| - SPECTRUM rewrite: sinc lobes, waterfall, tilt-to-tune dial, |
| filters (VULN/SOFT/HIDDEN). It's an instrument now. |
| |
| - CHARGING MODE: voltage curves, charge estimation. |
| The pig rests. The heap breathes. |
| |
| - SD FORMAT: quick/full. From the menu. No more laptops. |
| |
| - FileServer brew loop killed. LWIP poll wait instead. |
| The pig stopped punching itself in the heap. |
| |
| - Serial logging: compile-time eliminated in release. |
| PorkchopNullSerial. Zero-cost. |
| |
| - XP saves: CRC32 dirty tracking. No more redundant NVS. |
| |
+--------------------------------------------------------------------+
========================================================================
--[ 1 - THE HEAP FIGHT (A War Story in Four Acts)
this is the story the commit messages told.
"THE HEAP. THE HEAT. THE FIGHT."
seven commits. one sentence. repeated.
at some point the commits stopped being updates
and started being a mantra.
----[ ACT I: THE RESERVATION FENCE
the old boot sequence was a ritual.
5 phases. alloc/free patterns. hope-based defragmentation.
"allocate 50 blocks of 1KB. free them. allocate 20 blocks
of 3KB. free them. do a TLS test. pray."
it worked. mostly. on good days. with a tailwind.
the new approach is 3 lines of C.
void* fence = heap_caps_malloc(80000, MALLOC_CAP_8BIT);
preInitWiFiDriverEarly();
heap_caps_free(fence);
TLSF's good-fit allocator fills from the bottom. the 80KB
fence occupies the bottom. WiFi's permanent DMA/RX buffers
(~35KB) land ABOVE the fence. fence is freed. result:
60-80KB contiguous free space at the bottom. every time.
deterministic. no dancing. no prayers. no napkin math.
the old conditioning was a rain dance.
the fence is plumbing.
----[ ACT II: THE MONITORING SYSTEM
we used to fly blind. heap_caps_get_free_size() when we
remembered. diagnostic equivalent of checking engine oil
by the color of the smoke.
now there are three modules:
heap_policy.h -- every threshold in one file.
35KB for TLS. 80KB for Normal pressure. 30KB for Critical.
Adaptive cooldown: clamp(15s, 60s, 30s * largest/35000).
Frag penalty scale. EMA smoothing constants. All here.
heap_health.cpp -- the monitoring loop.
1Hz sampling. EMA-smoothed display values (asymmetric:
slow to drop, moderate to recover, absorbs transient spikes).
Graduated pressure (Normal > Caution > Warning > Critical)
with 3s hysteresis on de-escalation and 2-sample confirmation
on escalation (Critical = immediate, because when it's
Critical, you don't have time for a second opinion).
Session watermarks: write-once-if-lower. NEVER reset.
Not by brew. Not by resetPeaks. Not by anything.
The watermarks survived the heap fight. Trust them.
heap_gates.cpp -- the bouncers.
TLS gate: 35KB contiguous or go home.
Growth gate: frag ratio < 0.40 = denied.
Auto-brew blocked at Critical (brew needs 35KB,
Critical has <30KB, the math is not subtle).
SD writes blocked at Warning+ (FAT buffers allocate on heap).
All O(1). Read a cached uint8_t. No heap enumeration.
----[ ACT III: KNUTH WAS RIGHT
the commit said "KNUT WAS RIGHT ALL THIS TIME."
the typo was intentional. (it wasn't.)
Knuth's Fifty Percent Rule: in a steady-state allocator with
exponentially distributed lifetimes, the ratio of free blocks
to allocated blocks converges to 0.5.
we measured it. on our heap. during OINK mode. 0.48-0.52.
Knuth was right. In 1968. On a PDP-10. And again in 2026.
On an ESP32. With a cartoon pig on the screen.
above 0.7 = pathological fragmentation. more hole than cheese.
TLS won't fit. brewing won't help.
computed via heap_caps_get_info(). ~50us. not free.
solution: on-demand only when the diagnostics screen is active.
setKnuthEnabled(true) on show(), false on hide().
----[ ACT IV: THE SELF-INFLICTED BREW LOOP
the final commit message, translated from four languages
of frustration:
"heap jebany kurwa ja pierdole. i shot in my own leg."
the bug: FileServer::stop() called brewHeap() to "recover"
after web server shutdown. the brew enters promiscuous mode,
which allocates ~35KB of WiFi driver buffers. heap drops.
HeapHealth detects drop. conditionPending = true. main loop
calls maybeAutoConditionHeap(). which calls brewHeap().
which allocates driver buffers. which drops the heap.
low heap --> brew triggers --> WiFi alloc (-35KB)
^ |
| v
trigger again <-- health drops <-- more fragmentation
the pig was punching itself in the heap. every punch made it
angrier. every angry response was another punch.
FIX: FileServer::stop() no longer brews. polls heap metrics
for up to 500ms in 50ms intervals, waiting for LWIP's async
TCP/IP cleanup to finish naturally:
while ((millis() - waitStart) < kFileServerLwipWaitMaxMs) {
delay(kFileServerLwipPollMs);
if (curFree == prevFree && curLargest == prevLargest) break;
}
passive. patient. no WiFi driver activation. no recursive
brew triggers. the heap recovers when you stop hitting it.
cooldown floor raised: 5s -> 15s. because 5 seconds is not
enough for TLSF coalescing, FreeRTOS idle callbacks, and SPI
completion to settle. 15 seconds is. the difference between
a floor derived from napkins and a floor derived from watching
the device not crash.
--[ 2 - SPECTRUM: THE INSTRUMENT
HOG ON SPECTRUM was rewritten. not patched. rewritten.
7 commits of "SPECTRUM I AM ON THERE" before it compiled clean.
SINC LOBES: the old Gaussian approximation was pretty but wrong.
WiFi channels are sinc functions. sin(pi*d/BW) / (pi*d/BW).
bandwidth 11MHz. side lobes. first nulls at +/-11 MHz.
45-entry sinc LUT. precomputed. your SDR friends will stop wincing.
WATERFALL: 22-row circular buffer. 10 FPS. intensity from RSSI.
218px wide. the spectrum was a photograph. now it's a film.
DIAL MODE: hold the cardputer upright. IMU detects orientation.
tilt left: tune down. tilt right: tune up. channels 1-13.
space bar toggles channel lock. PPS counter in the corner.
an RF tuning dial operated by your wrist. like it's 1952.
we don't know why we built this. we don't regret it.
FILTERS: [F] cycles ALL / VULN / SOFT / HIDDEN.
VULN: OPEN, WEP, WPA only. SOFT: no PMF. HIDDEN: invisible
SSIDs. found anyway.
NOISE FLOOR: animated pixel noise at the baseline. cosmetic.
the RF equivalent of TV static. makes it breathe.
RENDER SNAPSHOT: SpectrumRenderNet[64] fixed array. WiFi task
writes to network vector. main loop snapshots to fixed array.
draw reads the snapshot. no races. no mutex in draw. heap-safe.
--[ 3 - NEW MODES
----[ CHARGING MODE [C]
press [C] from IDLE. NetworkRecon stops. GPS stops.
WiFi goes quiet. the heap exhales.
voltage-based battery percentage with separate charge/discharge
curves (Li-ion is not linear, the AXP lies, we interpolate).
11-point piecewise linear. charge rate from voltage slope over
30-second windows. estimated minutes to full. unplug detection.
it's the most boring feature we've ever shipped.
also the most requested.
----[ SD FORMAT [via TOOLS menu]
quick format and full format. from the device menu.
no laptop. no card reader. no "wait which filesystem."
FAT32 with fallback to wipe if the card is cursed.
--[ 4 - FIXES & INFRASTRUCTURE
----[ SERIAL LOGGING: COMPILE-TIME DEAD
Serial.printf() allocates temporary buffers on heap.
in a 300KB system, "not free" means "actively hostile."
PorkchopNullSerial: compile-time replacement. template
metaprogramming. every printf/print/println becomes a no-op.
the compiler eliminates it entirely.
release: PORKCHOP_LOG_ENABLED=0. debug: =1.
----[ XP SAVES: CRC32 DIRTY TRACKING
CRC32 (0xEDB88320, IEEE 802.3 reflected) of PorkXPData struct.
computed on load, recomputed before save. if equal: no write.
no NVS allocation. no flash wear. 14 lines of C.
----[ CAPLOERA SPI CONFLICT
SX1262 shares MOSI(G14)/MISO(G39)/SCK(G40) with the SD card.
GPIO5 is SX1262 CS. if not deasserted, SX1262 responds on the
bus and SD.begin() fails with f_mount(3).
fix: GPIO5 deasserted HIGH before M5Cardputer.begin().
CapLoRa GPS: SX1262 reset + CS deassert + G13 IOMUX clear
before GPS UART init. two chips. one bus. sorted.
----[ DONOHAM CRASH FIXES
"DONOHAM PROBABLY WONT CRASH" -- committed twice.
the second time with more conviction.
callback context violations in passive recon. deferred event
pattern enforced. XP no longer awarded from the WiFi task.
----[ NETWORKRECON: LEAN PORK
network quality scoring (RSSI + recency + activity + stability).
client count via bitset from data frames. freeNetworks() releases
the vector entirely on mode exit. ~8KB reclaimed during transfers.
----[ WARHOG PRESSURE-AWARE
replaced hardcoded heap threshold with getPressureLevel().
one consumer. using the system it was built for.
----[ WATERMARK PERSISTENCE
20-byte packed struct (magic 0x48574D4B). saved to SD every 60s.
pressure-gated (no writes at Warning+). loaded at boot.
session minimum free heap, minimum largest block, minimum health,
maximum pressure. never reset during session. not by brew.
not by resetPeaks(). not by anything. the abyss stared back.
we wrote down how deep it went.
----[ DIAGNOSTICS OVERHAUL
added: PRESSURE level, KNUTH ratio (live), PREV MIN/PREV LRG
(last session watermarks). removed: dead PSRAM section (the
device has no PSRAM; displaying "PSRAM: N/A" was adding insult
to architectural injury). removed: duplicate SD status display.
--[ 5 - HEAP NUMBERS (for the spreadsheet people)
all thresholds verified against heap_policy.h at HEAD.
Pressure Levels:
+----------+----------+----------+-----------+
| Level | Free | Frag | Behavior |
+----------+----------+----------+-----------+
| Normal | > 80KB | > 0.60 | all on |
| Caution | > 50KB | > 0.40 | shed load |
| Warning | > 30KB | > 0.25 | aggressive|
| Critical | < 30KB | < 0.25 | freeze |
+----------+----------+----------+-----------+
Adaptive Cooldown:
+---------------+-----------+
| Largest Block | Cooldown |
+---------------+-----------+
| 70KB+ | 60s (max) |
| 35KB | 30s |
| 17.5KB | 15s (min) |
| < 17.5KB | 15s floor |
+---------------+-----------+
Boot Sequence:
1. Serial.begin(115200)
2. SX1262 CS deassert (GPIO5 HIGH)
3. M5Cardputer.begin()
4. Reservation fence: 80KB alloc -> WiFi init -> fence free
5. Config load from SD
6. Previous session watermarks loaded
7. Display, audio, avatar init
8. GPS init (with CapLoRa SPI negotiation if applicable)
9. OinkMode.init(), WarhogMode.init(), Porkchop.init()
10. NetworkRecon.start()
11. HeapHealth.resetPeaks() -> real baseline, not 100%
--[ 6 - KNOWN ISSUES
- The heap is still 300KB. This will not change. Accept it.
(The pig accepted it. The pig is at peace.
The pig is lying. The pig is never at peace.)
- TLS still needs 35KB contiguous. If the heap is fragmented,
WiGLE and WPA-SEC uploads may fail. The gates will tell you why.
- CapLoRa GPS on non-ADV hardware: detected and warned.
Won't crash. Will disappoint.
--[ 7 - Legal
Educational and authorized security research ONLY.
The heap management system is not a weapon.
The spectrum analyzer is not a targeting system.
The client monitor is not a surveillance platform.
(They're all those things. Don't use them wrong.)
Don't be stupid. Don't be evil.
Don't deauth networks you don't own.
--[ 8 - Closing Transmission
the commit history of this release reads like the stages of grief.
it starts with "YELE YELE YELE. HEAPS DONT LIE."
it passes through "THE HEAP. THE HEAT. THE FIGHT."
it pauses at "KNUT WAS RIGHT ALL THIS TIME."
it stumbles into "fuck you heap."
it ends with "i shot in my own leg."
80+ commits between YDE and here:
- 7 said "SPECTRUM I AM ON THERE"
- 7 said "SD FORMAT FIXES"
- 4 said "THE HEAP. THE HEAT. THE FIGHT."
- 1 said "fuck you heap."
- 1 said it in Polish, Portuguese, and Spanish simultaneously.
- denial, anger, bargaining, depression, reservation fence.
the horse was unavailable for comment.
the horse was on ketamine.
the horse WAS the heap.
the barn was structurally sound.
team size: still 1.
coffee consumed: clinical.
sleep: distributed across insufficient intervals.
meds: taken (eventually).
shipped anyway.
if you've read this far, you either care too much
or not enough. either way: you're our kind of people.
proper mental innit.
OINK.
========================================================================
the confessional remains open:
github.com/0ct0sec/M5PORKCHOP/issues
coffee becomes code. code becomes heap fragmentation.
heap fragmentation becomes release notes.
fund the cycle: buymeacoffee.com/0ct0
praise the sun.
try finger but hole.
bajo jajo.
==[EOF]==