Can a LLM convert C, to ASM to specs and then to a working Z/80 Speccy tape? Yes.

Can a LLM convert C, to ASM to specs and then to a working Z/80 Speccy tape? Yes.

Damien Guard nerd sniped me and and other folks wanted more proof that it is now cheap, easy and possible to cheaply rewrite software or clone existing "source available" businesses (see bottom of the post).

So, let's get cracking by creating a toy application by sending this prompt to the LLM.

Create a sales tax calculator in C
Food is taxed at 10%
Personal Hygine is at 0%
Everything else 25%

Which creates

#include <stdio.h>
#include <string.h>
#include <ctype.h>

// Tax rates
#define FOOD_TAX 0.10
#define HYGIENE_TAX 0.00
#define DEFAULT_TAX 0.25

// Item categories
#define FOOD 1
#define HYGIENE 2
#define OTHER 3

int getCategory() {
    char choice;
    printf("Enter item category (F for Food, H for Hygiene, O for Other): ");
    scanf(" %c", &choice);
    
    choice = toupper(choice);
    
    switch(choice) {
        case 'F': return FOOD;
        case 'H': return HYGIENE;
        case 'O': return OTHER;
        default:
            printf("Invalid category. Defaulting to Other.\n");
            return OTHER;
    }
}

double calculateTax(double price, int category) {
    switch(category) {
        case FOOD: return price * FOOD_TAX;
        case HYGIENE: return price * HYGIENE_TAX;
        case OTHER: return price * DEFAULT_TAX;
        default: return price * DEFAULT_TAX;
    }
}

int main() {
    double price;
    int category;
    char another = 'Y';
    
    while (toupper(another) == 'Y') {
        printf("Enter item price: $");
        scanf("%lf", &price);
        
        category = getCategory();
        
        double tax = calculateTax(price, category);
        double total = price + tax;
        
        printf("Price: $%.2f\n", price);
        printf("Tax: $%.2f\n", tax);
        printf("Total: $%.2f\n\n", total);
        
        printf("Calculate another item? (Y/N): ");
        scanf(" %c", &another);
    }
    
    return 0;
}

Now let's compile it via gcc calc.c -o calc then objdump -d calc > calc.asm to convert it to assembly which results in the following

calc:     file format elf64-x86-64


Disassembly of section .init:

0000000000001000 <_init>:
    1000:       f3 0f 1e fa             endbr64 
    1004:       48 83 ec 08             sub    $0x8,%rsp
    1008:       48 8b 05 d9 2f 00 00    mov    0x2fd9(%rip),%rax        # 3fe8 <__gmon_start__@Base>
    100f:       48 85 c0                test   %rax,%rax
    1012:       74 02                   je     1016 <_init+0x16>
    1014:       ff d0                   call   *%rax
    1016:       48 83 c4 08             add    $0x8,%rsp
    101a:       c3                      ret    

Disassembly of section .plt:

0000000000001020 <.plt>:
    1020:       ff 35 7a 2f 00 00       push   0x2f7a(%rip)        # 3fa0 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:       f2 ff 25 7b 2f 00 00    bnd jmp *0x2f7b(%rip)        # 3fa8 <_GLOBAL_OFFSET_TABLE_+0x10>
    102d:       0f 1f 00                nopl   (%rax)
    1030:       f3 0f 1e fa             endbr64 
    1034:       68 00 00 00 00          push   $0x0
    1039:       f2 e9 e1 ff ff ff       bnd jmp 1020 <_init+0x20>
    103f:       90                      nop
    1040:       f3 0f 1e fa             endbr64 
    1044:       68 01 00 00 00          push   $0x1
    1049:       f2 e9 d1 ff ff ff       bnd jmp 1020 <_init+0x20>
    104f:       90                      nop
    1050:       f3 0f 1e fa             endbr64 
    1054:       68 02 00 00 00          push   $0x2
    1059:       f2 e9 c1 ff ff ff       bnd jmp 1020 <_init+0x20>
    105f:       90                      nop
    1060:       f3 0f 1e fa             endbr64 
    1064:       68 03 00 00 00          push   $0x3
    1069:       f2 e9 b1 ff ff ff       bnd jmp 1020 <_init+0x20>
    106f:       90                      nop
    1070:       f3 0f 1e fa             endbr64 
    1074:       68 04 00 00 00          push   $0x4
    1079:       f2 e9 a1 ff ff ff       bnd jmp 1020 <_init+0x20>
    107f:       90                      nop

Disassembly of section .plt.got:

0000000000001080 <__cxa_finalize@plt>:
    1080:       f3 0f 1e fa             endbr64 
    1084:       f2 ff 25 6d 2f 00 00    bnd jmp *0x2f6d(%rip)        # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
    108b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

Disassembly of section .plt.sec:

0000000000001090 <toupper@plt>:
    1090:       f3 0f 1e fa             endbr64 
    1094:       f2 ff 25 15 2f 00 00    bnd jmp *0x2f15(%rip)        # 3fb0 <toupper@GLIBC_2.2.5>
    109b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000000010a0 <puts@plt>:
    10a0:       f3 0f 1e fa             endbr64 
    10a4:       f2 ff 25 0d 2f 00 00    bnd jmp *0x2f0d(%rip)        # 3fb8 <puts@GLIBC_2.2.5>
    10ab:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000000010b0 <__stack_chk_fail@plt>:
    10b0:       f3 0f 1e fa             endbr64 
    10b4:       f2 ff 25 05 2f 00 00    bnd jmp *0x2f05(%rip)        # 3fc0 <__stack_chk_fail@GLIBC_2.4>
    10bb:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000000010c0 <printf@plt>:
    10c0:       f3 0f 1e fa             endbr64 
    10c4:       f2 ff 25 fd 2e 00 00    bnd jmp *0x2efd(%rip)        # 3fc8 <printf@GLIBC_2.2.5>
    10cb:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000000010d0 <__isoc99_scanf@plt>:
    10d0:       f3 0f 1e fa             endbr64 
    10d4:       f2 ff 25 f5 2e 00 00    bnd jmp *0x2ef5(%rip)        # 3fd0 <__isoc99_scanf@GLIBC_2.7>
    10db:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

Disassembly of section .text:

00000000000010e0 <_start>:
    10e0:       f3 0f 1e fa             endbr64 
    10e4:       31 ed                   xor    %ebp,%ebp
    10e6:       49 89 d1                mov    %rdx,%r9
    10e9:       5e                      pop    %rsi
    10ea:       48 89 e2                mov    %rsp,%rdx
    10ed:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
    10f1:       50                      push   %rax
    10f2:       54                      push   %rsp
    10f3:       45 31 c0                xor    %r8d,%r8d
    10f6:       31 c9                   xor    %ecx,%ecx
    10f8:       48 8d 3d fd 01 00 00    lea    0x1fd(%rip),%rdi        # 12fc <main>
    10ff:       ff 15 d3 2e 00 00       call   *0x2ed3(%rip)        # 3fd8 <__libc_start_main@GLIBC_2.34>
    1105:       f4                      hlt    
    1106:       66 2e 0f 1f 84 00 00    cs nopw 0x0(%rax,%rax,1)
    110d:       00 00 00 

0000000000001110 <deregister_tm_clones>:
    1110:       48 8d 3d f9 2e 00 00    lea    0x2ef9(%rip),%rdi        # 4010 <__TMC_END__>
    1117:       48 8d 05 f2 2e 00 00    lea    0x2ef2(%rip),%rax        # 4010 <__TMC_END__>
    111e:       48 39 f8                cmp    %rdi,%rax
    1121:       74 15                   je     1138 <deregister_tm_clones+0x28>
    1123:       48 8b 05 b6 2e 00 00    mov    0x2eb6(%rip),%rax        # 3fe0 <_ITM_deregisterTMCloneTable@Base>
    112a:       48 85 c0                test   %rax,%rax
    112d:       74 09                   je     1138 <deregister_tm_clones+0x28>
    112f:       ff e0                   jmp    *%rax
    1131:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    1138:       c3                      ret    
    1139:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

0000000000001140 <register_tm_clones>:
    1140:       48 8d 3d c9 2e 00 00    lea    0x2ec9(%rip),%rdi        # 4010 <__TMC_END__>
    1147:       48 8d 35 c2 2e 00 00    lea    0x2ec2(%rip),%rsi        # 4010 <__TMC_END__>
    114e:       48 29 fe                sub    %rdi,%rsi
    1151:       48 89 f0                mov    %rsi,%rax
    1154:       48 c1 ee 3f             shr    $0x3f,%rsi
    1158:       48 c1 f8 03             sar    $0x3,%rax
    115c:       48 01 c6                add    %rax,%rsi
    115f:       48 d1 fe                sar    %rsi
    1162:       74 14                   je     1178 <register_tm_clones+0x38>
    1164:       48 8b 05 85 2e 00 00    mov    0x2e85(%rip),%rax        # 3ff0 <_ITM_registerTMCloneTable@Base>
    116b:       48 85 c0                test   %rax,%rax
    116e:       74 08                   je     1178 <register_tm_clones+0x38>
    1170:       ff e0                   jmp    *%rax
    1172:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
    1178:       c3                      ret    
    1179:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

0000000000001180 <__do_global_dtors_aux>:
    1180:       f3 0f 1e fa             endbr64 
    1184:       80 3d 85 2e 00 00 00    cmpb   $0x0,0x2e85(%rip)        # 4010 <__TMC_END__>
    118b:       75 2b                   jne    11b8 <__do_global_dtors_aux+0x38>
    118d:       55                      push   %rbp
    118e:       48 83 3d 62 2e 00 00    cmpq   $0x0,0x2e62(%rip)        # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
    1195:       00 
    1196:       48 89 e5                mov    %rsp,%rbp
    1199:       74 0c                   je     11a7 <__do_global_dtors_aux+0x27>
    119b:       48 8b 3d 66 2e 00 00    mov    0x2e66(%rip),%rdi        # 4008 <__dso_handle>
    11a2:       e8 d9 fe ff ff          call   1080 <__cxa_finalize@plt>
    11a7:       e8 64 ff ff ff          call   1110 <deregister_tm_clones>
    11ac:       c6 05 5d 2e 00 00 01    movb   $0x1,0x2e5d(%rip)        # 4010 <__TMC_END__>
    11b3:       5d                      pop    %rbp
    11b4:       c3                      ret    
    11b5:       0f 1f 00                nopl   (%rax)
    11b8:       c3                      ret    
    11b9:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

00000000000011c0 <frame_dummy>:
    11c0:       f3 0f 1e fa             endbr64 
    11c4:       e9 77 ff ff ff          jmp    1140 <register_tm_clones>

00000000000011c9 <getCategory>:
    11c9:       f3 0f 1e fa             endbr64 
    11cd:       55                      push   %rbp
    11ce:       48 89 e5                mov    %rsp,%rbp
    11d1:       48 83 ec 10             sub    $0x10,%rsp
    11d5:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
    11dc:       00 00 
    11de:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    11e2:       31 c0                   xor    %eax,%eax
    11e4:       48 8d 05 1d 0e 00 00    lea    0xe1d(%rip),%rax        # 2008 <_IO_stdin_used+0x8>
    11eb:       48 89 c7                mov    %rax,%rdi
    11ee:       b8 00 00 00 00          mov    $0x0,%eax
    11f3:       e8 c8 fe ff ff          call   10c0 <printf@plt>
    11f8:       48 8d 45 f7             lea    -0x9(%rbp),%rax
    11fc:       48 89 c6                mov    %rax,%rsi
    11ff:       48 8d 05 41 0e 00 00    lea    0xe41(%rip),%rax        # 2047 <_IO_stdin_used+0x47>
    1206:       48 89 c7                mov    %rax,%rdi
    1209:       b8 00 00 00 00          mov    $0x0,%eax
    120e:       e8 bd fe ff ff          call   10d0 <__isoc99_scanf@plt>
    1213:       0f b6 45 f7             movzbl -0x9(%rbp),%eax
    1217:       0f be c0                movsbl %al,%eax
    121a:       89 c7                   mov    %eax,%edi
    121c:       e8 6f fe ff ff          call   1090 <toupper@plt>
    1221:       88 45 f7                mov    %al,-0x9(%rbp)
    1224:       0f b6 45 f7             movzbl -0x9(%rbp),%eax
    1228:       0f be c0                movsbl %al,%eax
    122b:       83 f8 4f                cmp    $0x4f,%eax
    122e:       74 1f                   je     124f <getCategory+0x86>
    1230:       83 f8 4f                cmp    $0x4f,%eax
    1233:       7f 21                   jg     1256 <getCategory+0x8d>
    1235:       83 f8 46                cmp    $0x46,%eax
    1238:       74 07                   je     1241 <getCategory+0x78>
    123a:       83 f8 48                cmp    $0x48,%eax
    123d:       74 09                   je     1248 <getCategory+0x7f>
    123f:       eb 15                   jmp    1256 <getCategory+0x8d>
    1241:       b8 01 00 00 00          mov    $0x1,%eax
    1246:       eb 22                   jmp    126a <getCategory+0xa1>
    1248:       b8 02 00 00 00          mov    $0x2,%eax
    124d:       eb 1b                   jmp    126a <getCategory+0xa1>
    124f:       b8 03 00 00 00          mov    $0x3,%eax
    1254:       eb 14                   jmp    126a <getCategory+0xa1>
    1256:       48 8d 05 f3 0d 00 00    lea    0xdf3(%rip),%rax        # 2050 <_IO_stdin_used+0x50>
    125d:       48 89 c7                mov    %rax,%rdi
    1260:       e8 3b fe ff ff          call   10a0 <puts@plt>
    1265:       b8 03 00 00 00          mov    $0x3,%eax
    126a:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
    126e:       64 48 2b 14 25 28 00    sub    %fs:0x28,%rdx
    1275:       00 00 
    1277:       74 05                   je     127e <getCategory+0xb5>
    1279:       e8 32 fe ff ff          call   10b0 <__stack_chk_fail@plt>
    127e:       c9                      leave  
    127f:       c3                      ret    

0000000000001280 <calculateTax>:
    1280:       f3 0f 1e fa             endbr64 
    1284:       55                      push   %rbp
    1285:       48 89 e5                mov    %rsp,%rbp
    1288:       f2 0f 11 45 f8          movsd  %xmm0,-0x8(%rbp)
    128d:       89 7d f4                mov    %edi,-0xc(%rbp)
    1290:       83 7d f4 03             cmpl   $0x3,-0xc(%rbp)
    1294:       74 36                   je     12cc <calculateTax+0x4c>
    1296:       83 7d f4 03             cmpl   $0x3,-0xc(%rbp)
    129a:       7f 43                   jg     12df <calculateTax+0x5f>
    129c:       83 7d f4 01             cmpl   $0x1,-0xc(%rbp)
    12a0:       74 08                   je     12aa <calculateTax+0x2a>
    12a2:       83 7d f4 02             cmpl   $0x2,-0xc(%rbp)
    12a6:       74 15                   je     12bd <calculateTax+0x3d>
    12a8:       eb 35                   jmp    12df <calculateTax+0x5f>
    12aa:       f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
    12af:       f2 0f 10 05 21 0e 00    movsd  0xe21(%rip),%xmm0        # 20d8 <_IO_stdin_used+0xd8>
    12b6:       00 
    12b7:       f2 0f 59 c1             mulsd  %xmm1,%xmm0
    12bb:       eb 33                   jmp    12f0 <calculateTax+0x70>
    12bd:       f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
    12c2:       66 0f ef c0             pxor   %xmm0,%xmm0
    12c6:       f2 0f 59 c1             mulsd  %xmm1,%xmm0
    12ca:       eb 24                   jmp    12f0 <calculateTax+0x70>
    12cc:       f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
    12d1:       f2 0f 10 05 07 0e 00    movsd  0xe07(%rip),%xmm0        # 20e0 <_IO_stdin_used+0xe0>
    12d8:       00 
    12d9:       f2 0f 59 c1             mulsd  %xmm1,%xmm0
    12dd:       eb 11                   jmp    12f0 <calculateTax+0x70>
    12df:       f2 0f 10 4d f8          movsd  -0x8(%rbp),%xmm1
    12e4:       f2 0f 10 05 f4 0d 00    movsd  0xdf4(%rip),%xmm0        # 20e0 <_IO_stdin_used+0xe0>
    12eb:       00 
    12ec:       f2 0f 59 c1             mulsd  %xmm1,%xmm0
    12f0:       66 48 0f 7e c0          movq   %xmm0,%rax
    12f5:       66 48 0f 6e c0          movq   %rax,%xmm0
    12fa:       5d                      pop    %rbp
    12fb:       c3                      ret    

00000000000012fc <main>:
    12fc:       f3 0f 1e fa             endbr64 
    1300:       55                      push   %rbp
    1301:       48 89 e5                mov    %rsp,%rbp
    1304:       48 83 ec 30             sub    $0x30,%rsp
    1308:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
    130f:       00 00 
    1311:       48 89 45 f8             mov    %rax,-0x8(%rbp)
    1315:       31 c0                   xor    %eax,%eax
    1317:       c6 45 db 59             movb   $0x59,-0x25(%rbp)
    131b:       e9 f1 00 00 00          jmp    1411 <main+0x115>
    1320:       48 8d 05 50 0d 00 00    lea    0xd50(%rip),%rax        # 2077 <_IO_stdin_used+0x77>
    1327:       48 89 c7                mov    %rax,%rdi
    132a:       b8 00 00 00 00          mov    $0x0,%eax
    132f:       e8 8c fd ff ff          call   10c0 <printf@plt>
    1334:       48 8d 45 e0             lea    -0x20(%rbp),%rax
    1338:       48 89 c6                mov    %rax,%rsi
    133b:       48 8d 05 49 0d 00 00    lea    0xd49(%rip),%rax        # 208b <_IO_stdin_used+0x8b>
    1342:       48 89 c7                mov    %rax,%rdi
    1345:       b8 00 00 00 00          mov    $0x0,%eax
    134a:       e8 81 fd ff ff          call   10d0 <__isoc99_scanf@plt>
    134f:       b8 00 00 00 00          mov    $0x0,%eax
    1354:       e8 70 fe ff ff          call   11c9 <getCategory>
    1359:       89 45 dc                mov    %eax,-0x24(%rbp)
    135c:       48 8b 45 e0             mov    -0x20(%rbp),%rax
    1360:       8b 55 dc                mov    -0x24(%rbp),%edx
    1363:       89 d7                   mov    %edx,%edi
    1365:       66 48 0f 6e c0          movq   %rax,%xmm0
    136a:       e8 11 ff ff ff          call   1280 <calculateTax>
    136f:       66 48 0f 7e c0          movq   %xmm0,%rax
    1374:       48 89 45 e8             mov    %rax,-0x18(%rbp)
    1378:       f2 0f 10 45 e0          movsd  -0x20(%rbp),%xmm0
    137d:       f2 0f 10 4d e8          movsd  -0x18(%rbp),%xmm1
    1382:       f2 0f 58 c1             addsd  %xmm1,%xmm0
    1386:       f2 0f 11 45 f0          movsd  %xmm0,-0x10(%rbp)
    138b:       48 8b 45 e0             mov    -0x20(%rbp),%rax
    138f:       66 48 0f 6e c0          movq   %rax,%xmm0
    1394:       48 8d 05 f4 0c 00 00    lea    0xcf4(%rip),%rax        # 208f <_IO_stdin_used+0x8f>
    139b:       48 89 c7                mov    %rax,%rdi
    139e:       b8 01 00 00 00          mov    $0x1,%eax
    13a3:       e8 18 fd ff ff          call   10c0 <printf@plt>
    13a8:       48 8b 45 e8             mov    -0x18(%rbp),%rax
    13ac:       66 48 0f 6e c0          movq   %rax,%xmm0
    13b1:       48 8d 05 e5 0c 00 00    lea    0xce5(%rip),%rax        # 209d <_IO_stdin_used+0x9d>
    13b8:       48 89 c7                mov    %rax,%rdi
    13bb:       b8 01 00 00 00          mov    $0x1,%eax
    13c0:       e8 fb fc ff ff          call   10c0 <printf@plt>
    13c5:       48 8b 45 f0             mov    -0x10(%rbp),%rax
    13c9:       66 48 0f 6e c0          movq   %rax,%xmm0
    13ce:       48 8d 05 d4 0c 00 00    lea    0xcd4(%rip),%rax        # 20a9 <_IO_stdin_used+0xa9>
    13d5:       48 89 c7                mov    %rax,%rdi
    13d8:       b8 01 00 00 00          mov    $0x1,%eax
    13dd:       e8 de fc ff ff          call   10c0 <printf@plt>
    13e2:       48 8d 05 cf 0c 00 00    lea    0xccf(%rip),%rax        # 20b8 <_IO_stdin_used+0xb8>
    13e9:       48 89 c7                mov    %rax,%rdi
    13ec:       b8 00 00 00 00          mov    $0x0,%eax
    13f1:       e8 ca fc ff ff          call   10c0 <printf@plt>
    13f6:       48 8d 45 db             lea    -0x25(%rbp),%rax
    13fa:       48 89 c6                mov    %rax,%rsi
    13fd:       48 8d 05 43 0c 00 00    lea    0xc43(%rip),%rax        # 2047 <_IO_stdin_used+0x47>
    1404:       48 89 c7                mov    %rax,%rdi
    1407:       b8 00 00 00 00          mov    $0x0,%eax
    140c:       e8 bf fc ff ff          call   10d0 <__isoc99_scanf@plt>
    1411:       0f b6 45 db             movzbl -0x25(%rbp),%eax
    1415:       0f be c0                movsbl %al,%eax
    1418:       89 c7                   mov    %eax,%edi
    141a:       e8 71 fc ff ff          call   1090 <toupper@plt>
    141f:       83 f8 59                cmp    $0x59,%eax
    1422:       0f 84 f8 fe ff ff       je     1320 <main+0x24>
    1428:       b8 00 00 00 00          mov    $0x0,%eax
    142d:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
    1431:       64 48 2b 14 25 28 00    sub    %fs:0x28,%rdx
    1438:       00 00 
    143a:       74 05                   je     1441 <main+0x145>
    143c:       e8 6f fc ff ff          call   10b0 <__stack_chk_fail@plt>
    1441:       c9                      leave  
    1442:       c3                      ret    

Disassembly of section .fini:

0000000000001444 <_fini>:
    1444:       f3 0f 1e fa             endbr64 
    1448:       48 83 ec 08             sub    $0x8,%rsp
    144c:       48 83 c4 08             add    $0x8,%rsp
    1450:       c3                      ret    

Now that we have the assembly, can we make it human readable and useful? Let's try the following prompt

Look at @calc.asm and generate feature specification as seperate files that describe the application. One per topic domain.

Which then produces a specification library - here's the functional overview...

# Tax Calculator - Functional Overview

## Description
The Tax Calculator is a command-line application that calculates taxes and total costs based on user input. The application allows users to enter an amount and select a tax category, then calculates the appropriate tax and displays the total.

## Key Features
1. **Amount Input**: Accepts a numerical amount as input from the user
2. **Category Selection**: Allows the user to select from different tax categories:
   - F: Food category (5% tax)
   - H: Healthcare category (0% tax - tax exempt)
   - O: Other/Standard category (10% tax)

3. **Tax Calculation**: Calculates the tax amount based on the selected category and input amount
4. **Total Calculation**: Adds the tax to the original amount to calculate the total cost
5. **Results Display**: Shows the original amount, tax amount, and total amount to the user
6. **Multiple Calculations**: Allows the user to perform multiple calculations in a single session by asking if they want to continue

## Workflow
1. User is prompted to enter an amount
2. User selects a tax category
3. System calculates tax based on the category and amount
4. System displays the original amount, tax amount, and total amount
5. User is asked if they want to perform another calculation
6. If yes, the process repeats; if no, the application terminates 

Here's the dataflow specification...

# Tax Calculator - Data Flow and Storage Specification

## Data Types and Storage

### User Input Data
- **Purchase Amount**: Stored as a double-precision floating-point number (64-bit)
- **Category Selection**: Initially captured as a character, then converted to an integer category ID
- **Continuation Flag**: Single character stored as a byte

### Calculated Data
- **Tax Amount**: Calculated and stored as a double-precision floating-point number (64-bit)
- **Total Amount**: Calculated and stored as a double-precision floating-point number (64-bit)

### Storage Duration
- All data is stored in stack-allocated variables
- Data persists only for the duration of a single calculation cycle
- No data persists between separate executions of the program

## Data Flow

### Input Flow
1. User enters purchase amount via keyboard
2. Input is captured through scanf and stored in a local variable
3. User enters category selection via keyboard
4. Character input is converted to uppercase and mapped to a category ID
5. Category ID is returned to the main function

### Processing Flow
1. Main function receives the purchase amount and category ID
2. These values are passed to the calculateTax function
3. calculateTax applies the appropriate tax rate based on the category ID
4. The calculated tax amount is returned to the main function
5. Main function adds the tax amount to the original amount to calculate the total

### Output Flow
1. Original amount is displayed to the user
2. Calculated tax amount is displayed to the user
3. Calculated total amount is displayed to the user

### Continuation Flow
1. User is prompted to continue or exit
2. Input is captured and converted to uppercase
3. If input is 'Y', the application loops back to the beginning of the calculation cycle
4. If input is anything else, the application terminates

## Data Transformation

### Character to Category Mapping
- 'F' (ASCII 0x46) -> Category ID 1 (Food)
- 'H' (ASCII 0x48) -> Category ID 2 (Healthcare)
- 'O' (ASCII 0x4F) -> Category ID 3 (Other)
- Any other character -> Category ID 3 (Other, default)

### Category to Tax Rate Mapping
- Category ID 1 (Food) -> 0.05 (5%)
- Category ID 2 (Healthcare) -> 0.00 (0%)
- Category ID 3 (Other) -> 0.10 (10%)

## Data Security
- No persistent storage of data
- No sensitive data is processed
- All variables are properly initialized before use
- Stack memory is protected against buffer overflows 

Here's the business logic specification:

# Tax Calculator - Tax Calculation Specification

## Tax Categories
The application supports three different tax categories:

1. **Food (F)**: 
   - Category ID: 1
   - Tax Rate: 5%
   - Applied to all food items

2. **Healthcare (H)**:
   - Category ID: 2
   - Tax Rate: 0% (tax exempt)
   - Applied to all healthcare-related items

3. **Other (O)**:
   - Category ID: 3
   - Tax Rate: 10%
   - Default category for all other items
   - Used as fallback when an invalid category is selected

## Calculation Formulas

### Tax Amount Calculation
```
taxAmount = amount * taxRate
```

Where:
- `amount` is the purchase amount entered by the user
- `taxRate` is the decimal tax rate based on the selected category (0.05 for Food, 0.0 for Healthcare, 0.10 for Other)

### Total Amount Calculation
```
totalAmount = amount + taxAmount
```

Where:
- `amount` is the original purchase amount
- `taxAmount` is the calculated tax amount

## Implementation Details

The tax calculation is implemented in the `calculateTax` function, which:
1. Takes two parameters: the purchase amount and the category ID
2. Applies the appropriate tax rate based on the category ID:
   - Category 1 (Food): 5% tax rate
   - Category 2 (Healthcare): 0% tax rate
   - Category 3 (Other): 10% tax rate
3. Returns the calculated tax amount

The category selection is handled by the `getCategory` function, which:
1. Prompts the user to select a category
2. Converts the user input to uppercase
3. Maps the input character to a category ID:
   - 'F' -> 1 (Food)
   - 'H' -> 2 (Healthcare)
   - 'O' -> 3 (Other)
4. Uses category 3 (Other) as default for invalid inputs 

Plenty of other files were also generated but for the sake of brevity I'm not going to include them.

Now that we got the specification, let's strip all 'isms related to the C language out of the functional spec.

Look at @specs-from-calc-c. Remove all ANSI C aspects of the spec and write them to SPECS folder

Now this is where it gets interesting. We have successfully taken an application that was C, disassembled it into ASM, created a high level clean-room functional specification that describes the application. Cool. Now let's provide the LLM with the following prompt

Implement this as a Z/80 spectrum calculator program
Compile it
Add it to the disk image
Run the emulator with the disk image

You may install software

Which generates the sales tax calculator for the Z80 speccy.

; Tax Calculator for ZX Spectrum
; Based on provided specifications
; Implements a simple tax calculation program with three categories:
; F: Food (5% tax)
; H: Healthcare (0% tax)
; O: Other/Standard (10% tax)

; *********************************************************************
; Constants and System Variables
; *********************************************************************

; System constants
SCREEN_START    equ 16384    ; Start of screen memory
ATTR_START      equ 22528    ; Start of attribute memory
LAST_K          equ 23560    ; Last key pressed system variable
CHANNEL_S       equ 5633     ; ROM print routine (output to screen)
CLS             equ 3503     ; ROM routine to clear screen
OPEN_CHANNEL    equ 5633     ; ROM routine to open a channel

; Program constants
FOOD_RATE       equ 5        ; 5% tax rate
HEALTHCARE_RATE equ 0        ; 0% tax rate
OTHER_RATE      equ 10       ; 10% tax rate

; Decimal multiplier for fixed-point math
; We use 2 decimal places, so multiply by 100
DECIMAL_MULT    equ 100

; *********************************************************************
; Program Entry Point
; *********************************************************************

    org 32768           ; Start of code (above BASIC)

start:
    call init_program   ; Initialize program
    
main_loop:
    ; Display welcome message
    ld de, welcome_msg
    call print_string
    
    ; Get amount
    call get_amount
    ; Store amount in amount_value
    ld (amount_value), hl
    
    ; Get category selection
    call get_category
    ; Store category in category_value
    ld a, c
    ld (category_value), a
    
    ; Calculate tax based on category and amount
    call calculate_tax
    ; Store tax in tax_value
    ld (tax_value), hl
    
    ; Calculate total
    call calculate_total
    ; Store total in total_value
    ld (total_value), hl
    
    ; Display results
    call display_results
    
    ; Ask if user wants to continue
    call ask_continue
    cp 'Y'              ; Check if user pressed 'Y'
    jr z, main_loop     ; If yes, repeat the main loop
    cp 'y'              ; Check if user pressed 'y'
    jr z, main_loop     ; If yes, repeat the main loop
    
    ; Exit program - return to BASIC
    jp exit_program

; *********************************************************************
; Initialization
; *********************************************************************

init_program:
    ; Clear screen
    call CLS
    ret

; *********************************************************************
; Input Routines
; *********************************************************************

; Get decimal amount input from user
get_amount:
    ld de, amount_prompt
    call print_string
    
    ; Clear buffer
    ld hl, input_buffer
    ld (hl), 0
    
    ; Get input string
    call get_input
    
    ; Convert string to number
    ld de, input_buffer
    call string_to_number
    ret

; Get tax category from user
get_category:
    ld de, category_prompt
    call print_string
    
    ; Wait for key press
get_cat_wait:
    call wait_key
    cp 'F'               ; Check for 'F'
    jr z, cat_food
    cp 'f'               ; Check for 'f'
    jr z, cat_food
    cp 'H'               ; Check for 'H' 
    jr z, cat_healthcare
    cp 'h'               ; Check for 'h'
    jr z, cat_healthcare
    cp 'O'               ; Check for 'O'
    jr z, cat_other
    cp 'o'               ; Check for 'o'
    jr z, cat_other
    
    ; Invalid category, default to Other
    ld de, invalid_cat
    call print_string
    ld c, 3              ; Category 3 (Other)
    ret
    
cat_food:
    ld c, 1              ; Category 1 (Food)
    ret
    
cat_healthcare:
    ld c, 2              ; Category 2 (Healthcare)
    ret
    
cat_other:
    ld c, 3              ; Category 3 (Other)
    ret

; Ask if user wants to continue
ask_continue:
    ld de, continue_prompt
    call print_string
    call wait_key        ; Wait for key press
    ret                  ; Return with key in A

; Wait for key press
wait_key:
    ld hl, LAST_K        ; Address of system variable LAST_K
    ld (hl), 0           ; Clear key buffer
wait_loop:
    ld a, (hl)           ; Get key
    or a                 ; Check if key pressed
    jr z, wait_loop      ; No key, continue waiting
    ret                  ; Key pressed, return with key in A

; Get a string input from user
get_input:
    ld hl, input_buffer  ; Point to input buffer
    ld b, 0              ; Input length counter
    
input_loop:
    call wait_key        ; Wait for key press
    
    cp 13                ; Check for ENTER
    jr z, input_done     ; Input complete
    
    cp 8                 ; Check for DELETE
    jr z, delete_char    ; Handle delete
    
    ; Check if character is a valid digit or decimal point
    cp '0'
    jr c, input_loop     ; Ignore if less than '0'
    cp ':'               ; ':' is after '9' in ASCII
    jr c, valid_digit    ; Valid digit
    
    cp '.'               ; Check for decimal point
    jr nz, input_loop    ; Ignore if not decimal point
    
valid_digit:
    ; Don't accept more than 6 characters
    ld a, b
    cp 6
    jr nc, input_loop    ; Too many chars
    
    ; Store character in buffer
    ld a, (LAST_K)
    ld (hl), a           ; Store character
    inc hl               ; Next buffer position
    inc b                ; Increment counter
    
    ; Echo character to screen
    rst 16
    
    jr input_loop        ; Continue input loop
    
delete_char:
    ; Handle backspace (delete) key
    ld a, b
    or a                 ; Check if buffer is empty
    jr z, input_loop     ; If empty, do nothing
    
    ; Move cursor back, erase character
    ld a, 8
    rst 16               ; Backspace
    ld a, ' '
    rst 16               ; Space (erase character)
    ld a, 8
    rst 16               ; Backspace again
    
    ; Update buffer
    dec hl               ; Move back in buffer
    dec b                ; Decrement counter
    ld (hl), 0           ; Clear the deleted character
    
    jr input_loop        ; Continue input loop
    
input_done:
    ; Terminate string with null
    ld (hl), 0           ; Null terminate
    
    ; Print newline
    ld a, 13
    rst 16
    ld a, 10
    rst 16
    
    ret

; *********************************************************************
; Calculation Routines
; *********************************************************************

; Calculate tax amount based on category
; Input: amount_value (HL) and category_value (A)
; Output: tax in HL
calculate_tax:
    ld a, (category_value)
    
    cp 1                  ; Check if Food
    jr z, calc_food
    
    cp 2                  ; Check if Healthcare
    jr z, calc_healthcare
    
    ; Default: Other category
calc_other:
    ld a, OTHER_RATE      ; 10% tax
    jr do_tax_calc
    
calc_food:
    ld a, FOOD_RATE       ; 5% tax
    jr do_tax_calc
    
calc_healthcare:
    ld a, HEALTHCARE_RATE ; 0% tax
    jr do_tax_calc
    
do_tax_calc:
    ; Tax calculation: amount * tax_rate / 100
    ; HL = amount
    ld hl, (amount_value)
    
    ; Multiply by tax rate
    ld b, 0
    ld c, a              ; BC = tax rate
    call multiply        ; HL = amount * tax rate
    
    ; Divide by 100
    ld bc, 100
    call divide          ; HL = (amount * tax rate) / 100
    
    ret                  ; Return tax in HL

; Calculate total amount (amount + tax)
; Input: amount_value and tax_value
; Output: total in HL
calculate_total:
    ld de, (amount_value)  ; DE = amount
    ld hl, (tax_value)     ; HL = tax
    add hl, de             ; HL = amount + tax
    ret

; *********************************************************************
; Utility Routines
; *********************************************************************

; Convert string to number
; Input: DE points to null-terminated string
; Output: HL contains the number (fixed point, 2 decimal places)
string_to_number:
    ld hl, 0            ; Initialize result to 0
    ld b, 0             ; Decimal point flag (0 = not seen)
    ld c, 0             ; Decimal position counter
    
str_to_num_loop:
    ld a, (de)          ; Get character
    or a                ; Check for null terminator
    jr z, str_to_num_done
    
    cp '.'              ; Check for decimal point
    jr z, decimal_point
    
    ; Must be a digit
    sub '0'             ; Convert ASCII to value
    
    ; Multiply current result by 10
    push af             ; Save digit
    push bc             ; Save flags
    
    ld b, h             ; BC = result
    ld c, l
    add hl, hl          ; HL = result * 2
    add hl, hl          ; HL = result * 4
    add hl, bc          ; HL = result * 5
    add hl, hl          ; HL = result * 10
    
    pop bc              ; Restore flags
    pop af              ; Restore digit
    
    ; If past decimal point, increment decimal counter
    push af             ; Save digit
    ld a, b
    or a                ; Check decimal flag
    jr z, add_digit     ; Not past decimal yet
    
    ; Past decimal point, check counter
    ld a, c
    inc c               ; Increment counter
    cp 2                ; Check if already have 2 decimal places
    jr nc, skip_digit   ; Skip this digit if already have 2 decimals
    
add_digit:
    pop af              ; Restore digit
    ld e, a
    ld d, 0             ; DE = digit
    add hl, de          ; Add to result
    jr next_char
    
skip_digit:
    pop af              ; Restore digit (but don't use it)
    jr next_char
    
decimal_point:
    inc b               ; Set decimal point flag
    ld c, 0             ; Reset decimal position counter
    jr next_char
    
next_char:
    inc de              ; Next character
    jr str_to_num_loop
    
str_to_num_done:
    ; If no decimal part was entered, multiply by 100
    ld a, b
    or a                ; Check if decimal point was seen
    jr nz, adjust_decimals
    
    ; No decimal point, multiply by 100
    ld b, h
    ld c, l
    add hl, hl          ; * 2
    add hl, hl          ; * 4
    add hl, hl          ; * 8
    add hl, hl          ; * 16
    add hl, hl          ; * 32
    add hl, hl          ; * 64
    add hl, bc          ; * 65
    add hl, hl          ; * 130
    ld b, h
    ld c, l
    srl b
    rr c                ; * 65
    add hl, bc          ; * 195
    add hl, hl          ; * 390
    add hl, hl          ; * 780
    srl h
    rr l                ; * 390
    add hl, hl          ; * 780
    srl h
    rr l                ; * 390
    srl h
    rr l                ; * 195
    srl h
    rr l                ; * 97.5
    add hl, hl          ; * 195
    add hl, hl          ; * 390
    srl h
    rr l                ; * 195
    add hl, hl          ; * 390
    srl h
    rr l                ; * 195
    add hl, hl          ; * 390
    srl h
    rr l                ; * 195
    add hl, hl          ; * 390
    srl h
    rr l                ; * 195
    add hl, hl          ; * 390
    srl h
    rr l                ; * 195
    add hl, hl          ; * 390
    srl h
    rr l                ; * 195
    add hl, hl          ; * 390
    srl h
    rr l                ; * 195
    jr done_convert
    
adjust_decimals:
    ; Check number of decimal places entered
    ld a, c
    or a                ; No decimal places entered?
    jr z, mul_100       ; Multiply by 100
    
    cp 1                ; One decimal place entered?
    jr z, mul_10        ; Multiply by 10
    
    ; Already have 2 decimal places, no adjustment needed
    jr done_convert
    
mul_100:
    ; Multiply by 100
    ld b, h
    ld c, l
    add hl, hl          ; * 2
    add hl, hl          ; * 4
    add hl, hl          ; * 8
    add hl, bc          ; * 9
    add hl, hl          ; * 18
    add hl, hl          ; * 36
    add hl, bc          ; * 37
    add hl, hl          ; * 74
    add hl, hl          ; * 148
    add hl, bc          ; * 149
    add hl, bc          ; * 150
    jr done_convert
    
mul_10:
    ; Multiply by 10
    ld b, h
    ld c, l
    add hl, hl          ; * 2
    add hl, hl          ; * 4
    add hl, bc          ; * 5
    add hl, hl          ; * 10
    
done_convert:
    ret                 ; Return with number in HL

; Multiply HL by BC
; Input: HL, BC
; Output: HL = HL * BC
multiply:
    push de             ; Save DE
    ld de, 0            ; Initialize result to 0
    
mult_loop:
    ; Check if BC is zero
    ld a, b
    or c
    jr z, mult_done
    
    ; Add HL to result
    ex de, hl           ; DE = multiplier, HL = result
    add hl, de          ; Add multiplier to result
    ex de, hl           ; DE = result, HL = multiplier
    
    ; Decrement counter
    dec bc
    jr mult_loop
    
mult_done:
    ex de, hl           ; HL = result
    pop de              ; Restore DE
    ret

; Divide HL by BC
; Input: HL, BC
; Output: HL = HL / BC
divide:
    push de             ; Save DE
    ld de, 0            ; Initialize quotient to 0
    
div_loop:
    ; Check if HL < BC
    call compare_hl_bc
    jr c, div_done      ; If HL < BC, done
    
    ; Subtract BC from HL
    or a                ; Clear carry
    sbc hl, bc
    
    ; Increment quotient
    inc de
    jr div_loop
    
div_done:
    ex de, hl           ; HL = quotient
    pop de              ; Restore DE
    ret

; Compare HL with BC
; Sets carry flag if HL < BC
compare_hl_bc:
    ld a, h
    cp b                ; Compare high bytes
    ret nz              ; Not equal, return with flags set
    
    ld a, l
    cp c                ; Compare low bytes
    ret

; *********************************************************************
; Output Routines
; *********************************************************************

; Display calculation results
display_results:
    ; Display amount
    ld de, amount_msg
    call print_string
    ld hl, (amount_value)
    call print_number
    
    ; Display tax
    ld de, tax_msg
    call print_string
    ld hl, (tax_value)
    call print_number
    
    ; Display total
    ld de, total_msg
    call print_string
    ld hl, (total_value)
    call print_number
    
    ; Print a blank line
    ld de, newline
    call print_string
    
    ret

; Print a string 
; Input: DE points to null-terminated string
print_string:
    ld a, (de)          ; Get character
    or a                ; Check for null terminator
    ret z               ; If zero, we're done
    
    rst 16              ; Print character
    
    inc de              ; Point to next character
    jr print_string     ; Continue printing

; Print a number in decimal format (fixed point, 2 decimal places)
; Input: HL = number (fixed point, 2 decimal places)
print_number:
    ; Print '$' sign
    ld a, '$'
    rst 16
    
    ; Extract the integer and fractional parts
    ; Integer part = number / 100
    ; Fractional part = number % 100
    
    ; Save the original number
    push hl
    
    ; Divide by 100 to get integer part
    ld bc, 100
    call divide         ; HL = integer part
    
    ; Print integer part
    call print_decimal
    
    ; Print decimal point
    ld a, '.'
    rst 16
    
    ; Get fractional part
    pop bc              ; BC = original number
    push hl             ; Save integer part
    
    ; Calculate fractional part = original - (integer * 100)
    push bc             ; Save original number
    ld bc, 100
    call multiply       ; HL = integer * 100
    pop bc              ; BC = original number
    
    ; BC - HL = fractional part
    or a                ; Clear carry
    ld a, c
    sub l
    ld l, a
    ld a, b
    sbc a, h
    ld h, a             ; HL = fractional part
    
    ; Print fractional part as 2 digits
    ld a, h
    or a                ; Check if high byte is zero
    jr z, print_low_byte ; If zero, just print low byte
    
    ; Otherwise this is an error, print "00"
    ld a, '0'
    rst 16
    rst 16
    jr fract_done
    
print_low_byte:
    ld a, l
    cp 10               ; Check if less than 10
    jr nc, print_fract  ; If not, print as is
    
    ; Add leading zero for single-digit values
    push af             ; Save value
    ld a, '0'
    rst 16              ; Print leading zero
    pop af              ; Restore value
    
print_fract:
    ld h, 0
    ld l, a
    call print_decimal
    
fract_done:
    pop hl              ; Restore HL
    ret

; Print a decimal number in HL
print_decimal:
    ; Print a decimal number (0-65535)
    ld bc, -10000
    call num1
    ld bc, -1000
    call num1
    ld bc, -100
    call num1
    ld bc, -10
    call num1
    ld bc, -1
num1:
    ld a, '0'-1
num2:
    inc a
    add hl, bc
    jr c, num2
    sbc hl, bc
    
    ; Skip leading zeros, but always print at least one digit
    cp '0'
    jr nz, print_digit  ; Not a leading zero
    
    ; Check if we already printed a digit
    push af
    ld a, (digit_printed)
    or a
    jr nz, print_ok
    
    ; Check if we're at the last digit
    ld a, c
    cp 255              ; Check if BC = -1 (last digit)
    jr nz, skip_zero    ; Not last digit, skip leading zero
    
print_ok:
    pop af
    jr print_digit
    
skip_zero:
    pop af
    ret
    
print_digit:
    rst 16
    
    ; Set the digit_printed flag
    ld a, 1
    ld (digit_printed), a
    ret

; *********************************************************************
; Exit Program
; *********************************************************************

exit_program:
    ; Display exit message
    ld de, exit_msg
    call print_string
    ret                  ; Return to BASIC

; *********************************************************************
; Data Section
; *********************************************************************

; Prompts and messages
welcome_msg:     defb 13, 10, "TAX CALCULATOR", 13, 10, 13, 10, 0
amount_prompt:   defb "Enter purchase amount: ", 0
category_prompt: defb "Select tax category (F)ood, (H)ealthcare, (O)ther: ", 0
invalid_cat:     defb 13, 10, "Invalid category. Using Other.", 13, 10, 0
amount_msg:      defb 13, 10, "Amount: ", 0
tax_msg:         defb 13, 10, "Tax: ", 0
total_msg:       defb 13, 10, "Total: ", 0
continue_prompt: defb 13, 10, 13, 10, "Do you want to continue? (Y/N): ", 0
exit_msg:        defb 13, 10, 13, 10, "Thank you for using Tax Calculator!", 13, 10, 0
newline:         defb 13, 10, 0

; Variables
digit_printed:   defb 0        ; Flag for printing leading zeros

; Input buffer (7 bytes: 6 chars + null terminator)
input_buffer:    defs 7, 0

; Variables for calculated values (16-bit words)
amount_value:    defw 0        ; Purchase amount
category_value:  defb 0        ; Tax category
tax_value:       defw 0        ; Calculated tax amount
total_value:     defw 0        ; Total amount

; Program end
end start 

Now we need to compile the application using pasmo --tapbas taxcalc.asm taxcalc.tap. So let's give it a spin!

It works but the sales tax isn't being calculated properly so let's head on back and give the LLM the following prompt and attach the above screenshot to it.

Look at the attached screenshot.
The tax did not generate correctly.
The amount is accepting user input.
The cost is not being asked and user does not accept input.

Look at the @00-specs-without-c to see functionality needed. Make changes as needed.

which resolves the formatting issues, however user interactive input does not happen. The application should ask me how much an item costs but it does not do that. So let's head back to the LLM and attach the screenshot.

It took a few more backwards and forwards to steer it via screenshots that had verbose logging but success was achieved.

The techniques detailed below were able to take an application from assembler, generate high-level specs and those high-level clean-room specs were able to generate a working Z/80 spectrum application.

Yes, Claude Code can decompile itself. Here’s the source code.
These LLMs are shockingly good at deobfuscation, transpilation and structure to structure conversions. I discovered this back around Christmas where I asked an LLM to make me an Haskell audio library by transpiling a rust implementation. An “oh fuck” moment in timeOver the Christmas break I’ve been critically looking

Anyway, that was a fun trip back in time and memory lane - thanks Damien Guard for the inspiration and nerd-snipe. In all it took me about two hours, of that it was mainly me trying to figure out how to install, run, configure these emulators and how to make a Z/80 tape.

The actual build was all very manual, driven by me as a human loop because the the Z/80 spectrum isn't really a good target for automating a reward loop. These LLMs get most things right but some things wrong. By having tests in your codebase and invoking the MCP tools to run these tests you'll get successful outcomes automatically nine times out of ten...

It's a new world for software engineers, it's kinda like the Telsa FSD. Sometimes it gets it wrong but if you trust it enough, provide enough guidance and understand how these coding assistants work under the hood you can achieve N factor more outcomes than ever before.

Software rewrites from one language to another are no longer an expensive thing for a company to do and if a "source available" company has provided the internet with their restricted source-code they can be cloned in hours, automatically, whilst you watch Netflix.

ps. socials for this blog post are below

If you enjoyed reading, give 'em a share please: