Hello Everyone!
The curiosities of turning a PNG file into an operational 'Pixel-Silicon die'!
Or... use the technical title:
"A specification for 24-bit Planar VRAM ligation, enabling the encapsulation of x86 hardware engines (WASM), BIOS, and nested 1.44MB substrates within a unified 1024-wide Pixel-Silicon die."
Alight, I have been grinding away on this new side project named chromatical-core the last few days... after having made that post about the pixelator project... where I show a method for encoding various file types as PNG images.
That project's post also detailed loading the PNG files into a browser and launching a v86 linux instance with them but it is not really 'required reading' for the post that I am now writing.
Basically, the whole reason that I developed the 'pixelator' system (aside from what I mention in that previous post) was so that I could use it (specifically) the PNG files as both a 'memory' for an LLM and hopefully a tiny operating system for the LLM all stored in the browser's 'localstorage' which I also connect to the 'url hash' as part of a 'persistence mechanism' but detailing all that would require a post all its own.
Suffice it to say that one thing lead to another and I realized that an interesting approach would be to create one PNG image file that is a 1.44 MB blank 'floppy disk img file' and then nest it inside of a 2.88 MB 'floppy disk' PNG and build as much 'control logic' into the PNG itself as I could.
Without going into an insane amount of details about accidentally corrupting the encoded files, or buggery with v86 complaining constantly, or having to create blob URL(s) and handling mechanisms for them to deal with the nested files.... and on and on... I eventually hammered out a rather straightforward methodology that can be found here which (although a very early implementation) seems to work rather well as a proof of concept.
I also put together some troubleshooting material for anyone tinkering with the v86 side of things and it can be found here. Hopefully, it can spare folks the hours and hours of headache that I encountered trying to get everything functional.
Along the way, that part of things would have been made easier if I had not accidentally corrupted my WASM file during the initial 'packaging' procedure but honestly I am glad that it happened because it exposed a pretty bad flaw in my methodology for dealing with zeros in the original packaging script. Once that got fixed things assuredly went much smoother... because I was no longer trying to load a corrupted WASM file!
Okay, I thought this post would require a heck of a lot more words to spell things out but there you have it. I am going to spare everyone all the technical jargon that can be found in the chromatical-core repository but below I will include an example of the single html file that I use... and I will use the PNG file that the html page uses as this post's image... but please note that server-side compression will probably make it where it cannot be decoded. If you want to try loading the image file (with the html included below in this post) it can be found here.
My next steps (that I should be working on right now instead of writing this) is to build in IPFS support and couple the IPFS parts with my latest 'ai kernel' (system prompt) so that I can further my blockchain support goals. Having finally gotten my latest 'ai kernel' into a more stable state... doing everything else should not be all that difficult.
Here is the html loader that I have been using. It can also be found here.
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="background:#000; color:#fff; font-family:monospace; margin:0; padding:15px; overflow:hidden;">
<div style="display:flex; justify-content:space-between; border-bottom:1px solid #333; padding-bottom:5px; font-size:12px;">
<span>IRONVAULT V8.1 // TRUNCATION_AWARE
<span id="stat-bar" style="color:#0f0;">[AWAITING_LIGATION]</span>
</div>
<div id="os-selector" style="margin-top:10px; display:flex; gap:10px; flex-wrap:wrap;">
<span style="color:#666">Scanning Carrier Registry...</span>
</div>
<div id="screen_container" style="width:640px; height:400px; border:1px solid #222; background:#000; margin-top:10px; overflow:hidden; position:relative;">
<canvas style="width:640px; height:400px; image-rendering:pixelated;"></canvas>
</div>
<pre id="term" style="height:calc(100vh - 500px); overflow-y:auto; border:1px solid #222; padding:10px; margin-top:10px; white-space:pre-wrap; background:#050505; color:#0f0; font-size:13px;">> System Initializing...</pre>
<script>
let carrier = null;
let registry = {};
const sysFiles = ["libv86.js", "v86.wasm", "seabios.bin", "vgabios.bin"];
function log(m) {
const t = document.getElementById('term');
t.innerText += "\n> " + m;
t.scrollTop = t.scrollHeight;
}
async function init() {
try {
const res = await fetch("CARRIER_STORAGE_v1.png");
const blob = await res.blob();
const bitmap = await createImageBitmap(blob, { colorSpaceConversion: 'none' });
const canvas = document.createElement('canvas');
canvas.width = 1024; canvas.height = 961;
const ctx = canvas.getContext('2d');
ctx.drawImage(bitmap, 0, 0);
const imgData = ctx.getImageData(0, 0, 1024, 961).data;
carrier = new Uint8Array(1024 * 960 * 3);
for (let i = 1024 * 4, j = 0; i < imgData.length; i += 4) {
carrier[j++] = imgData[i]; carrier[j++] = imgData[i+1]; carrier[j++] = imgData[i+2];
}
const dv = new DataView(carrier.buffer);
const fileCount = dv.getUint32(0, true);
const selector = document.getElementById('os-selector');
selector.innerHTML = "BOOT: ";
for (let i = 0; i < fileCount; i++) {
const base = 4 + (i * 24);
let nameBytes = carrier.subarray(base, base + 16);
let zeroIdx = nameBytes.indexOf(0);
let name = new TextDecoder().decode(nameBytes.subarray(0, zeroIdx !== -1 ? zeroIdx : 16)).trim();
registry[name] = { offset: dv.getUint32(base + 16, true), size: dv.getUint32(base + 20, true) };
// TRICK: Exclusion Filter - if it's not a system component, it's a BOOT option
if (!sysFiles.includes(name)) {
const btn = document.createElement('button');
btn.innerText = name.toUpperCase();
btn.style = "background:#fff; color:#000; border:0; padding:5px 10px; cursor:pointer; font-weight:bold; font-family:inherit;";
btn.onclick = () => ignite(name);
selector.appendChild(btn);
}
}
document.getElementById('stat-bar').innerText = "[CARRIER_ONLINE]";
log("Ligation Success. All images detected (including truncated names).");
} catch (e) { log("LIGATION_ERROR: " + e.message); }
}
function getAtomicBuffer(name) {
const meta = registry[name];
const sub = carrier.subarray(meta.offset, meta.offset + meta.size);
const out = new Uint8Array(sub.length);
out.set(sub);
return out;
}
async function ignite(osName) {
if (!carrier) return;
document.getElementById('os-selector').style.display = "none";
log(`Igniting OS: ${osName}...`);
try {
const jsText = new TextDecoder().decode(getAtomicBuffer("libv86.js")).replace(/\0/g, '').trim();
const jsURL = URL.createObjectURL(new Blob([jsText], { type: 'text/javascript' }));
const script = document.createElement('script');
script.src = jsURL;
script.onload = () => {
let V86 = window.V86Starter || window.v86Starter || window.N;
if (!V86) {
for (let k in window) { if (k.toLowerCase().includes("v86") && typeof window[k] === "function") { V86 = window[k]; break; } }
}
const fdaRaw = getAtomicBuffer(osName);
const emu = new V86({
wasm_path: URL.createObjectURL(new Blob([getAtomicBuffer("v86.wasm").buffer], {type: 'application/wasm'})),
memory_size: 128 * 1024 * 1024, // 128MB for DOS stability
vga_canvas: document.querySelector("canvas"),
screen_container: document.getElementById("screen_container"),
bios: { buffer: getAtomicBuffer("seabios.bin").buffer },
vga_bios: { buffer: getAtomicBuffer("vgabios.bin").buffer },
fda: { buffer: fdaRaw.buffer },
autostart: true,
boot_order: 0x1,
serial0_active: true,
disable_speaker: true,
disable_mouse: true
});
let lastCount = 0;
let wedgeTriggered = false;
setInterval(() => {
const count = emu.get_instruction_counter();
if (count > lastCount) {
document.getElementById('stat-bar').innerText = `CPU_PULSE: ${count.toLocaleString()} | OS: ${osName.toUpperCase()}`;
lastCount = count;
}
// Handshake for SectorForth only
if (osName.toLowerCase().includes("sector") && !wedgeTriggered && lastCount > 5500000) {
wedgeTriggered = true;
emu.keyboard_send_scancodes([0x1C, 0x9C]);
emu.serial0_send("\n\n123 .\n");
}
}, 1000);
emu.add_listener("serial0-output-char", (c) => {
document.getElementById('term').innerText += c;
if (document.getElementById('term').innerText.length > 5000) document.getElementById('term').innerText = document.getElementById('term').innerText.slice(-2000);
document.getElementById('term').scrollTop = document.getElementById('term').scrollHeight;
});
window.addEventListener("keydown", (e) => {
if (e.key === "Enter") emu.serial0_send("\n");
else if (e.key === "Backspace") emu.serial0_send("\b");
else if (e.key.length === 1) emu.serial0_send(e.key);
});
log(`Ignition Successful.`);
};
document.head.appendChild(script);
} catch (e) { log("FATAL: " + e.message); }
}
window.onload = init;
</script>
</body>
</html>
Thanks for reading!
Please check out the Homesteading Community!
Cheers! & Hive On!
All content found in this post is mine!