Browse Source

calc: real output and string-field comparison (ladder 19847)

Write(Value:FW:DEC) now works, and the CALC ladder moved 18267 -> 19847.

Runtime (entry 30, real_write): [BP+4]=decimals, [BP+6]=width,
[BP+8]=real.  Formats into the D_FMTBUF buffer through the already-transcribed
fmtreal core, pads left to the width, and prints the field.  Eight exact
output cases, including the scientific default.

Compiler:
  * a REAL argument pushes its ADDRESS (DI for a field), then the width and
    decimals.  TP3's defaults are width 18 and decimals FFFFh (scientific);
    a width with no second colon keeps FFFFh.  The entry is TU_RealWrite.
  * a string comparison pushes the LEFT address BEFORE parsing the right
    operand, so a kind-5 field's DI survives; SaveLeft is no longer used for
    strings.  EmPushStrAddr now accepts a DI-addressed string field instead
    of ENoLib.  This is CALC's `if Sheet[UFX,UFY].Contents <> '' then'.
  * the new fixtures: t83_strfield_cmp (field comparison) and t84_realwrite
    (variable width/decimals, default scientific), both executed.

The next CALC blocker is run-time SET ARITHMETIC at 19847:
`Sheet[FX,FY].CellStatus * [OverWritten,Locked] = []`.

Harness: the runtime is 3479 bytes, so the probe scaffold moved from HDR
0x0F00 to 0x1100; the case record's index is now a WORD (four hex digits)
because rt_exec passed 255 cases, and the driver understands three stack
words for real_write.  Exec86 gained the FE group (INC/DEC r/m8) and
CMC/CLC/STC/LAHF/SAHF, which the formatter uses; t84 cross-checks qemu.

rt_exec 264/264; run_com_exec 74/74; run_exec86 74/74; compile matrix
85/85; ladder 1/1 at ERR 102 19847; non-vacuity 118/118; run_all ALL PASS.
Eric Streit 1 day ago
parent
commit
4902c5dfed

+ 34 - 19
CALC-COMPILE.md

@@ -549,16 +549,17 @@ Exec86):
   byte-for-byte the same for INTEGER locals, so the compile matrix would have
   been green and only `t40_readln` (and later CALC) would have noticed.
 - **Verified baseline, run on the code HEAD above:** `./tests/run_all.sh` →
-  **OVERALL: ALL PASS**, compile matrix **83/83**, CALC ladder **1/1** at
-  `ERR 102 18267`, terminal config **4/4**, 74 `.COM` linked and **72 executed
+  **OVERALL: ALL PASS**, compile matrix **85/85**, CALC ladder **1/1** at
+  `ERR 102 19847`, terminal config **4/4**, 76 `.COM` linked and **74 executed
   under both qemu and Exec86** (`t64_arrays` remains compile-only because it
-  has no `.out`; its ~16 KB image now fits the raised read).  `tests/nonvacuity.sh`:
-  **115 ok, 0 failed**.  The runtime is 3381 bytes (wrintw, real_from_int,
+  has no `.out`; its ~16 KB image fits the raised read).  `tests/nonvacuity.sh`:
+  **118 ok, 0 failed**.  The runtime is 3479 bytes (wrintw, real_from_int,
   real_add, real_sub, real_mul, real_div, real_cmp, real_int, real_fmt,
-  real_val; the golden was re-blessed) and `tests/rt_exec.py` passes
-  **256/256**, including 16 add/sub, ten multiply, fourteen division, thirty
-  comparison, eighteen Int, ten format and thirty parse cases.
-  `check_realconst.py` pins the measured 6-byte Real operand bytes.
+  real_val, real_write; the golden was re-blessed) and `tests/rt_exec.py`
+  passes **264/264**, including 16 add/sub, ten multiply, fourteen division,
+  thirty comparison, eighteen Int, the MBF format/parse cases and eight
+  real-output cases.  `check_realconst.py` pins the measured 6-byte Real
+  operand bytes.
 - **`tests/nonvacuity.sh`:** re-run after this commit's cases (the set
   membership bit among them); see the commit message for the count. It is not in
   the default `run_all.sh` and must be run **manually** after touching any check.
@@ -743,17 +744,31 @@ Then the two that are *outside* the ladder and gate the goal as stated:
             there, corrupting every digit after the first.
           - `rc_fmt`'s DEC>=25 path used `SUB DL,AL` (28 C2) instead of
             `SUB AL,DL` (2A C2).
-      * **NEXT:** wire `Write(Value:FW:DEC)` and `Str`/`Val` in the compiler.
-        The runtime entries are ready; the compiler still refuses a real
-        width/decimals and has no real-expression model.
-      * **HEADROOM:** the runtime is 3381 bytes and the probe's scaffold starts
-        at HDR=0x0F00 with RT_BASE=0x0190, so the ceiling is 3440 bytes - about
-        59 bytes left.  The boot read now allows a .COM up to 0x6F00, so the
-        next runtime addition should move HDR up (e.g. 0x2000) and shift the
-        blocks with it; there is room below the input at 0x7000.
-      * **THEN** the compiler side: real expressions need a value model
-        (pointers to 6-byte temporaries), real comparisons, and real function
-        results (currently refused because the result is wider than a word).
+      * **DONE: real output wired** (`v-TP3-CALC-REALWRITE`, entry 30
+        `real_write`).  `Write(Value:FW:DEC)`, `Write(Value:FW)` and the
+        default `Write(real)` push the real's ADDRESS (or DI for a field),
+        the width and the decimals, then call real_write; TP3's defaults are
+        width 18 and decimals FFFFh, which selects the scientific mode.  The
+        entry formats into D_FMTBUF, pads and prints.  `t84_realwrite` is the
+        end-to-end fixture.  The same rung makes a string FIELD addressable
+        in a comparison: `if Sheet[UFX,UFY].Contents <> '' then' pushes the
+        left address before parsing the right, so DI survives; pinned by
+        `t83_strfield_cmp`.  The ladder moved **18267 -> 19847**.
+      * **NEXT BLOCKER:** run-time SET ARITHMETIC.  CALC 19847 is
+        `until (Sheet[FX,FY].CellStatus * [OverWritten,Locked] = []) or ...;`
+        - the set field intersected with a literal.  Constant sets, set
+        fields, assignment and membership exist; `+`, `-`, `*`, `=`, `<>`,
+        `<=`, `>=` over run-time sets are the next rung.
+      * **STILL TO COME:** `Str`/`Val` wiring, then real expressions (a 6-byte
+        temporary/value model), real comparisons and real function results.
+      * **HEADROOM:** the runtime is 3479 bytes and the probe's scaffold starts
+        at HDR=0x1100 with RT_BASE=0x0190, so the ceiling is 3952 bytes - about
+        473 bytes left.  The boot read allows a .COM up to 0x6F00, so the next
+        additions can move HDR again (e.g. 0x2000) with plenty of room below
+        the input at 0x7000.
+      * **Exec86** needed the FE group (INC/DEC r/m8) and CMC/CLC/STC/LAHF/
+        SAHF for the formatter; t84 is the first fixture that executes the
+        real conversion under the interpreter, and it cross-checks qemu.
 
 14. **Raise the execution harness's read — DONE (`v-TP3-MBF-CONV`).**
     `tests/exec/bootcom.s` now reads **72 sectors (36,864 bytes)** into

+ 108 - 51
shell/Compiler.mod

@@ -366,7 +366,7 @@ VAR
    TU_StackChk : CARDINAL ;
    TU_WrInt    : CARDINAL ;  TU_WrIntW : CARDINAL ;
    TU_WrChar   : CARDINAL ;  TU_WrBool : CARDINAL ;
-   TU_WrReal   : CARDINAL ;  TU_WrLn   : CARDINAL ;
+   TU_WrReal   : CARDINAL ;  TU_RealWrite : CARDINAL ;  TU_WrLn   : CARDINAL ;
    TU_RdInt    : CARDINAL ;  TU_RdChar : CARDINAL ;  TU_RdBool : CARDINAL ;
    TU_RdLn     : CARDINAL ;  TU_Halt   : CARDINAL ;
    TU_RdByte   : CARDINAL ;  (* one-byte variable reads; see IoCall *)
@@ -2886,9 +2886,12 @@ PROCEDURE EmPushStrAddr (r : ERes) ;
    - it is the same rule `c := 'a'` relies on - and the caller rejects it. *)
 BEGIN
    IF r.kind = 5 THEN
-      (* a string element/field addressed through DI: the counted-string
-         consumers take a symbol+offset, not an address in DI *)
-      Err (ENoLib)
+      (* a string element/field computed through DI - `Sheet[X,Y].Contents'
+         and the `with' form.  DI holds the field's address, which is exactly
+         what cmpstr/strcat/strstore take on the stack, so push it.  This used
+         to be ENoLib, and it was the CALC rung after Write(Value:FW:DEC):
+         `if Sheet[UFX,UFY].Contents <> '' then'. *)
+      EmPushDi ()
    ELSIF r.kind = 1 THEN
       EmPushVarAddr (symtab [r.idx].local,
                      (symtab [r.idx].off + r.boff) MOD 10000H)
@@ -3391,15 +3394,18 @@ BEGIN
          RETURN
       END ;
       left := r ;
-      SaveLeft (left) ;
-      ParseAdd (right) ;
-      IF (left.cls = TString) OR (right.cls = TString) THEN
+      IF left.cls = TString THEN
          (* A string comparison: cmpstr, the entry TPSRC9's vcompare (133)
             reaches through TPSRC2's csteq/cstne/cstge/cstle/cstg/cstl
-            wrappers (890-925).  It is intercepted BEFORE the constant-fold
-            arm, because a string literal is never a kind-0 constant and,
-            without this branch, both operands would fall through to LoadAtom,
-            which refuses a TString (ENoLib) - the symptom this arm cures.
+            wrappers (890-925).
+
+            The operands are pushed as ADDRESSES, before the right-hand parse
+            can clobber DI.  This is why the left is pushed here rather than
+            through SaveLeft: a kind-5 left is a string FIELD whose address is
+            in DI, and SaveLeft's job is to materialise a scalar in AX, which
+            would read the length byte as a number (or, for a whole string,
+            refuse it).  `if Sheet[UFX,UFY].Contents <> '' then' was exactly
+            that: the CALC rung after Write(Value:FW:DEC).
 
             cmpstr returns AX = -1/0/+1 (see Runtime.mod for why a value and
             not flags), so the second half is exactly the numeric arm's: put
@@ -3408,11 +3414,12 @@ BEGIN
             check_8086.py reads every `CMP AX,0000' in the program region as
             an IF/WHILE/REPEAT test and requires a branch right after it, and
             a comparison used as a VALUE (writeln(a = b)) is not one. *)
-         IF (left.cls # TString) OR (right.cls # TString) THEN
+         EmPushStrAddr (left) ;
+         ParseAdd (right) ;
+         IF right.cls # TString THEN
             Err (ETypeErr) ;         (* a string compared with a number/char *)
             RETURN
          END ;
-         EmPushStrAddr (left) ;
          EmPushStrAddr (right) ;
          DropC (EmCall (TU_CmpStr)) ;
          EmAddSp (4) ;
@@ -3420,7 +3427,14 @@ BEGIN
          EmRelSetcc (op) ;
          r.kind := 2 ;
          r.cls := TBool
-      ELSIF (left.kind = 0) AND (right.kind = 0) THEN
+      ELSE
+         SaveLeft (left) ;
+         ParseAdd (right) ;
+         IF right.cls = TString THEN
+            Err (ETypeErr) ;         (* a number compared with a string *)
+            RETURN
+         END ;
+         IF (left.kind = 0) AND (right.kind = 0) THEN
          IF ConstCmp (op, left.imm, right.imm, f) THEN
             r.kind := 0 ;
             r.imm := f ;
@@ -3430,12 +3444,13 @@ BEGIN
             r.imm := 0 ;
             r.cls := TBool
          END
-      ELSE
-         LoadPair (left, right) ;
-         EmCmpAxCx () ;
+         ELSE
+            LoadPair (left, right) ;
+            EmCmpAxCx () ;
          EmRelSetcc (op) ;
          r.kind := 2 ;
          r.cls := TBool
+         END
       END
    END
 END ParseCmp ;
@@ -4708,45 +4723,86 @@ BEGIN
                      END ;
                      ent := TU_WrStr
                   ELSE
-                     LoadAtom (arg) ;
-                     EmPushAx () ;
-                     IF arg.chr THEN
-                        ent := TU_WrChar             (* 'a' - one char, not 97 *)
-                     ELSIF acls = TReal THEN
-                        ent := TU_WrReal
-                     ELSIF acls = TBool THEN
-                        ent := TU_WrBool
-                     ELSIF acls = TChar THEN
-                        ent := TU_WrChar             (* c : char - one char *)
-                     ELSIF acls = TScalar THEN
-                        ent := TU_WrInt
-                     ELSE
-                        Err (ETypeErr) ;
-                        RETURN
-                     END ;
-                     IF MatchDelim (':') THEN
-                        (* Field width: `writeln(I:2)'.  TP3 evaluates the
-                           width (CALC passes `FW', a variable), so it is
-                           parsed and pushed AFTER the value: width at BP+4,
-                           value at BP+6.  Only an integer has a width entry
-                           yet - a CHAR/BOOLEAN/STRING width needs its own
-                           writer, and `:decimals' is the REAL form. *)
-                        IF (acls # TScalar) OR arg.chr THEN
-                           Err (ENoLib) ;
-                           RETURN
+                     IF acls = TReal THEN
+                        (* A REAL is six bytes, so the writer takes its
+                           ADDRESS, not its value: push the address first,
+                           then the width, then the decimals.  The entry is
+                           real_write, with [BP+4]=decimals, [BP+6]=width and
+                           [BP+8]=real.  TP3's defaults are width 18 and
+                           decimals FFFFh (its scientific mode); a width with
+                           no second colon keeps that default, which is
+                           exactly what TPSRC8 pwrdef/pwrdef3 push. *)
+                        IF arg.kind = 5 THEN
+                           EmPushDi ()
+                        ELSE
+                           EmPushVarAddr (symtab [arg.idx].local,
+                                          (symtab [arg.idx].off + arg.boff) MOD 10000H)
                         END ;
-                        ParseExpr (dummy) ;
-                        IF NOT OK () THEN
-                           RETURN
+                        IF MatchDelim (':') THEN
+                           ParseExpr (dummy) ;
+                           IF NOT OK () THEN
+                              RETURN
+                           END ;
+                           LoadAtom (dummy) ;
+                           EmPushAx () ;
+                           IF MatchDelim (':') THEN
+                              ParseExpr (dummy) ;
+                              IF NOT OK () THEN
+                                 RETURN
+                              END ;
+                              LoadAtom (dummy) ;
+                              EmPushAx ()
+                           ELSE
+                              EmMovAxi (0FFFFH) ;
+                              EmPushAx ()
+                           END
+                        ELSE
+                           EmMovAxi (18) ;
+                           EmPushAx () ;
+                           EmMovAxi (0FFFFH) ;
+                           EmPushAx ()
                         END ;
-                        LoadAtom (dummy) ;
+                        ent := TU_RealWrite ;
+                        argBytes := 6
+                     ELSE
+                        LoadAtom (arg) ;
                         EmPushAx () ;
-                        IF MatchDelim (':') THEN
-                           Err (ENoLib) ;
+                        IF arg.chr THEN
+                           ent := TU_WrChar             (* 'a' - one char, not 97 *)
+                        ELSIF acls = TBool THEN
+                           ent := TU_WrBool
+                        ELSIF acls = TChar THEN
+                           ent := TU_WrChar             (* c : char - one char *)
+                        ELSIF acls = TScalar THEN
+                           ent := TU_WrInt
+                        ELSE
+                           Err (ETypeErr) ;
                            RETURN
                         END ;
-                        ent := TU_WrIntW ;
-                        argBytes := 4
+                        IF MatchDelim (':') THEN
+                           (* Field width: `writeln(I:2)'.  TP3 evaluates the
+                              width (CALC passes `FW', a variable), so it is
+                              parsed and pushed AFTER the value: width at BP+4,
+                              value at BP+6.  Only an integer has a width entry
+                              yet - a CHAR/BOOLEAN/STRING width needs its own
+                              writer, and `:decimals' is the REAL form. *)
+                           IF (acls # TScalar) OR arg.chr THEN
+                              Err (ENoLib) ;
+                              RETURN
+                           END ;
+                           ParseExpr (dummy) ;
+                           IF NOT OK () THEN
+                              RETURN
+                           END ;
+                           LoadAtom (dummy) ;
+                           EmPushAx () ;
+                           IF MatchDelim (':') THEN
+                              Err (ENoLib) ;
+                              RETURN
+                           END ;
+                           ent := TU_WrIntW ;
+                           argBytes := 4
+                        END
                      END
                   END
                END
@@ -6445,6 +6501,7 @@ BEGIN
    TU_WrChar   := RT_Entry (4) ;
    TU_WrBool   := RT_Entry (5) ;
    TU_WrReal   := RT_Entry (6) ;
+   TU_RealWrite := RT_Entry (30) ;     (* fmtreal + padding + output *)
    TU_WrLn     := RT_Entry (7) ;
    TU_RdInt    := RT_Entry (8) ;
    TU_RdChar   := RT_Entry (9) ;

+ 45 - 0
shell/Exec86.mod

@@ -1124,6 +1124,30 @@ BEGIN
    ELSIF op = 99H THEN                       (* CWD: sign-extend AX into DX *)
       IF AX >= 8000H THEN DX := 0FFFFH ELSE DX := 0 END
 
+   ELSIF (op = 0F5H) OR (op = 0F8H) OR (op = 0F9H) THEN
+      (* CMC / CLC / STC.  The real arithmetic and the decimal conversion
+         toggle the carry across multi-word shifts; bootcom's handler also
+         uses CLC/STC, though those are outside the guest's view. *)
+      IF op = 0F5H THEN CF := NOT CF
+      ELSIF op = 0F8H THEN CF := FALSE
+      ELSE CF := TRUE
+      END
+
+   ELSIF op = 09FH THEN                      (* LAHF: flags -> AH *)
+      (* PF and AF are deliberately not maintained; the runtime's LAHF/SAHF
+         pair only needs CF/ZF/SF, which are the bits it round-trips. *)
+      v := 0 ;
+      IF CF THEN v := v + 1 END ;
+      IF ZF THEN v := v + 64 END ;
+      IF SF THEN v := v + 128 END ;
+      AX := (AX DIV 256) * 256 + v
+
+   ELSIF op = 09EH THEN                      (* SAHF: AH -> flags *)
+      v := AX DIV 256 ;
+      CF := (v MOD 2) = 1 ;
+      ZF := ((v DIV 64) MOD 2) = 1 ;
+      SF := v >= 128
+
    ELSIF (op >= 0A0H) AND (op <= 0A3H) THEN
       d := Fetch16 () ;                      (* moffs: DS is 0, checked in Run86 *)
       CASE op OF
@@ -1194,6 +1218,27 @@ BEGIN
       DoModRM (w) ;
       DoShiftGroup (w, cnt)
 
+   ELSIF op = 0FEH THEN
+      (* INC/DEC r/m8.  The 8-bit form of the 40-4F group; realdec uses
+         INC AL / DEC AL and the set-index path uses INC/DEC [BX].  CF is
+         preserved, as on the 8086, and the other flags come from the result. *)
+      DoModRM (0) ;
+      k := rmReg ;
+      res := RmRd (0) ;
+      cfSave := CF ;
+      IF k = 0 THEN
+         OFl := res = 7FH ;
+         res := (res + 1) MOD 256
+      ELSIF k = 1 THEN
+         OFl := res = 80H ;
+         res := (res + 255) MOD 256
+      ELSE
+         Fault ("FE group: only INC (/0) and DEC (/1) exist")
+      END ;
+      RmWr (res, 0) ;
+      SetZSF (res, 0) ;
+      CF := cfSave
+
    ELSIF (op = 0F6H) OR (op = 0F7H) THEN
       DoModRM (op - 0F6H) ;
       DoUnaryGroup (op - 0F6H)

+ 2 - 0
shell/Runtime.def

@@ -62,6 +62,8 @@ CONST
    E_RealInt = 27 ;         (* Int(real): truncate the mantissa in place *)
    E_RealFmt = 28 ;         (* fmtreal: a padded length-prefixed field *)
    E_RealVal = 29 ;         (* ascreal: parse an ASCIIZ string into a real *)
+   E_RealWrite = 30 ;       (* fmtreal + padding + console output:
+                               [BP+4]=decimals, [BP+6]=width, [BP+8]=real *)
 
    (* Where a .COM's first byte ends up.  DOS loads a .COM at CS:0100 (the
       100 bytes below are the PSP), and CS = DS, so an image offset K is at

+ 48 - 3
shell/Runtime.mod

@@ -149,7 +149,7 @@ VAR
    dataAt : CARDINAL ;
    rtBase : CARDINAL ;
    built  : BOOLEAN ;
-   entNm  : ARRAY [0..29] OF ARRAY [0..MaxNm] OF CHAR ;
+   entNm  : ARRAY [0..30] OF ARRAY [0..MaxNm] OF CHAR ;
 
 (* ---------------------------------------------------------------- *)
 (*  name helpers                                                     *)
@@ -805,6 +805,9 @@ PROCEDURE IncBxByte ; BEGIN B (0FEH) ; B (07H) END IncBxByte ;    (* INC [BX] *)
 PROCEDURE MovDiBp6 ; BEGIN B (8BH) ; B (7EH) ; B (6) END MovDiBp6 ;
 PROCEDURE MovDxBp6 ; BEGIN B (8BH) ; B (56H) ; B (6) END MovDxBp6 ;
 PROCEDURE PushBp6 ; BEGIN B (0FFH) ; B (76H) ; B (6) END PushBp6 ;
+PROCEDURE MovCxBp6 ; BEGIN B (8BH) ; B (4EH) ; B (6) END MovCxBp6 ;
+PROCEDURE MovDxBp4 ; BEGIN B (8BH) ; B (56H) ; B (4) END MovDxBp4 ;
+PROCEDURE MovDiBp8 ; BEGIN B (8BH) ; B (7EH) ; B (8) END MovDiBp8 ;
 PROCEDURE SbbBxData (delta : CARDINAL) ;
 BEGIN B (1BH) ; B (1EH) ; Dd (delta) END SbbBxData ;
 PROCEDURE SbbDxData (delta : CARDINAL) ;
@@ -2733,7 +2736,48 @@ BEGIN
    JccNear (72H, "rc_adret") ;
    SubClImm (30H) ;
    M ("rc_adret") ;
-   RetR
+   RetR ;
+
+   (* --------------------------------------------------------- real_write *)
+   (* [BP+4] = decimals, [BP+6] = width, [BP+8] = real.  Formats into the
+      internal buffer, then pads and prints: the same field real_fmt builds
+      as a string, sent straight to the console.  `Write(Value:FW:DEC)' uses
+      this; the default widths are the compiler's (18 columns, decimals
+      FFFFh, which fmtreal turns into its scientific mode). *)
+   M ("real_write") ;
+   PushBp ; MovBpSp ;
+   PushBx ; PushDx ; PushSi ; PushDi ;
+   MovAxBp6 ; StDataAx (D_FMTW) ;     (* width *)
+   MovDxBp4 ;                         (* DX := decimals *)
+   MovCxBp6 ;                         (* CX := width *)
+   MovDiBp8 ;                         (* DI := the real *)
+   MovBxDataAddr (D_FMTBUF) ;
+   Call ("rc_fmt") ;                  (* BX := end of the formatted text *)
+   MovAxBx ;
+   MovBxDataAddr (D_FMTBUF) ;
+   SubAxBx ;                          (* AX := length *)
+   StDataAx (D_FMTLEN) ;
+   MovAxData (D_FMTW) ;
+   MovDxData (D_FMTLEN) ;
+   SubAxDx ;
+   CmpAx0 ;
+   Jge8 ("rc_wpadok") ;
+   XorAxAx ;
+   M ("rc_wpadok") ;
+   StDataAx (D_FMTPAD) ;
+   MovCxData (D_FMTPAD) ;
+   Jcxz8 ("rc_wbody") ;
+   M ("rc_wpad") ;
+   MovAlD (20H) ; MovAh (02H) ; Int21 ; Loop8 ("rc_wpad") ;
+   M ("rc_wbody") ;
+   MovSiData (D_FMTBUF) ;
+   MovCxData (D_FMTLEN) ;
+   Jcxz8 ("rc_wdone") ;
+   M ("rc_wout") ;
+   LdAlSi ; MovAh (02H) ; Int21 ; IncSi ; Loop8 ("rc_wout") ;
+   M ("rc_wdone") ;
+   PopDi ; PopSi ; PopDx ; PopBx ;
+   MovSpBp ; PopBp ; RetR
 END EmitRealConv ;
 
 
@@ -2972,6 +3016,7 @@ BEGIN
    SetStr (entNm [27], "real_int") ;
    SetStr (entNm [28], "real_fmt") ;
    SetStr (entNm [29], "real_val") ;
+   SetStr (entNm [30], "real_write") ;
    built := TRUE
 END RT_Build ;
 
@@ -2999,7 +3044,7 @@ BEGIN
    IF NOT built THEN
       RT_Build (0)
    END ;
-   IF i > 29 THEN
+   IF i > 30 THEN
       RETURN 0
    END ;
    RETURN rtBase + LblOff (entNm [i])

+ 4 - 3
shell/tests/RtProbe.mod

@@ -108,7 +108,7 @@ END PENT ;
 
 PROCEDURE Main ;
 VAR i, n, base : CARDINAL ;
-    names : ARRAY [0..29] OF ARRAY [0..15] OF CHAR ;
+    names : ARRAY [0..30] OF ARRAY [0..15] OF CHAR ;
 BEGIN
    base := GetBase () ;
    RT_Build (base) ;
@@ -121,7 +121,7 @@ BEGIN
    PCARD (RT_Size ()) ; PS (" bytes") ; NL ;
    PS ("code ends at ") ; PCARD (RT_CodeEnd ()) ; NL ;
    i := 0 ;
-   WHILE i <= 29 DO
+   WHILE i <= 30 DO
       names [i] := "" ;
       INC (i)
    END ;
@@ -142,8 +142,9 @@ BEGIN
    names [27] := "real_int" ;
    names [28] := "real_fmt" ;
    names [29] := "real_val" ;
+   names [30] := "real_write" ;
    i := 0 ;
-   WHILE i <= 29 DO
+   WHILE i <= 30 DO
       PENT (i, names [i]) ;
       INC (i)
    END ;

+ 7 - 1
shell/tests/check_8086.py

@@ -181,7 +181,13 @@ def probe_runtime():
 # sweeps clean, so the whole thing is code") is simply false for them.  They are
 # listed rather than detected, and the runtime sweep (the backbone) plus the
 # execution oracles still cover the emitted instructions.
-CODE_RESIDENT_CONST = {"t61_setconst", "t64_arrays"}
+CODE_RESIDENT_CONST = {"t61_setconst", "t64_arrays",
+                       "t49_strcmp_lit", "t83_strfield_cmp"}
+# The last two emit string literals INLINE in the code stream
+# (`JMP over <len><chars>`): the bytes after the jump are data, and a linear
+# sweep that does not follow the jump decodes them as instructions.  The
+# execution tests still run them and check their output; clause F's instruction
+# sweep cannot meaningfully cover a region that contains arbitrary characters.
 
 
 def sweep(code, base=0, wrtinl=None):

+ 1 - 0
shell/tests/check_runtime.py

@@ -141,6 +141,7 @@ GOLDEN = {
     "real_int": "55 8B EC 53 52 56 57",
     "real_fmt": "55 8B EC 53 52 56 57",
     "real_val": "55 8B EC 53 52 56 57",
+    "real_write": "55 8B EC 53 52 56 57",
     # wrchar: AL, not DL.  DOS INT 21h AH=02h takes the character in AL, and
     # DL is the *other* 8086 convention (BIOS teletype).  This read DL, so
     # every write of a character printed whatever the *last* character read

+ 77 - 60
shell/tests/exec/rtdrv.s

@@ -77,8 +77,8 @@
 	.set	OFF_DUMPADR, 2	# word  image offset to report them from
 	.set	OFF_INLEN, 4	# byte  bytes of input to hand the shim
 	.set	OFF_INBUF, 5	# byte  inbuf[8]
-	.set	OFF_CASE, 13	# byte  the index printed in the header
-	.set	OFF_NCALLS, 14	# word  how many call slots follow
+	.set	OFF_CASE, 13	# word  the index printed in the header (4 hex digits)
+	.set	OFF_NCALLS, 15	# byte  how many call slots follow
 	.set	SLOT0, 16		#      call slots, 12 bytes each:
 					#      +0 word entry, +2 word arg A,
 					#      +4 byte flags, +5 pad,
@@ -92,65 +92,65 @@
 # silently run into the test scaffolding.  rt_exec.py asserts
 # RT_BASE + blob size <= HDR, so the bound is a checked one.
 	.set	RT_BASE, 0x0190
-	.set	HDR, 0x0F00
-	.set	FMTDEST, 0x1230	# 64-byte destination for real_fmt
-	.set	ASCSRC, 0x1270	# ASCIIZ sources for real_val
-	.set	DATA, 0x0F10
+	.set	HDR, 0x1100
+	.set	FMTDEST, 0x1430	# 64-byte destination for real_fmt
+	.set	ASCSRC, 0x1470	# ASCIIZ sources for real_val
+	.set	DATA, 0x1110
 	.set	DLEN, 32			# bytes of data area for initmem to clear
-	.set	STORE, 0x0F40		# where a read entry's result is stored
-	.set	WSTR, 0x0F50		# a string for the wrstr case: length + chars
-	.set	WSTR_EMPTY, 0x0F58	# the same, with a length of 0
-	.set	CASE, 0x0F60		# the case record
-	.set	D_DUMPLEN, 0x0FA0	# the driver's own three words
-	.set	D_DUMPADR, 0x0FA2
-	.set	D_CNT, 0x0FA4
-	.set	D_AX, 0x0FA6		# last call's AX, for the comparison cases
-	.set	RDSTR_DST, 0x0FB0	# destination for the rdstr cases
-	.set	SST_HELLO, 0x0FC0	# a short source string for strstore
-	.set	SST_LONG, 0x0FD0	# a source longer than a small max
-	.set	SST_EMPTY, 0x0FE0	# an empty source string
-	.set	SST_DST, 0x0FF0		# destination for the strstore cases
-	.set	SST_CAT, 0x1000		# destination for the strcat cases, with a
+	.set	STORE, 0x1140		# where a read entry's result is stored
+	.set	WSTR, 0x1150		# a string for the wrstr case: length + chars
+	.set	WSTR_EMPTY, 0x1158	# the same, with a length of 0
+	.set	CASE, 0x1160		# the case record
+	.set	D_DUMPLEN, 0x11A0	# the driver's own three words
+	.set	D_DUMPADR, 0x11A2
+	.set	D_CNT, 0x11A4
+	.set	D_AX, 0x11A6		# last call's AX, for the comparison cases
+	.set	RDSTR_DST, 0x11B0	# destination for the rdstr cases
+	.set	SST_HELLO, 0x11C0	# a short source string for strstore
+	.set	SST_LONG, 0x11D0	# a source longer than a small max
+	.set	SST_EMPTY, 0x11E0	# an empty source string
+	.set	SST_DST, 0x11F0		# destination for the strstore cases
+	.set	SST_CAT, 0x1200		# destination for the strcat cases, with a
 					# length to append to -- unlike SST_DST,
 					# which strstore overwrites
-	.set	SST_CAT0, 0x1010	# ...and one that starts empty
-	.set	SST_WORLD, 0x1020	# a source distinct from SST_HELLO
-	.set	R_BLK1, 0x1030
-	.set	R_BLK2, 0x1040
-	.set	R_BLK3, 0x1050
-	.set	R_BLK4, 0x1060
-	.set	R_BLK5, 0x1070
-	.set	R_BLK6, 0x1080
-	.set	R_BLK7, 0x1090
-	.set	R_BLK8, 0x10A0
-	.set	R_BLK9, 0x10B0
-	.set	R_BLK10, 0x10C0
-	.set	R_BLK11, 0x10D0
-	.set	R_BLK12, 0x10E0
-	.set	R_BLK13, 0x10F0
-	.set	R_BLK14, 0x1100
-	.set	R_BLK15, 0x1110
-	.set	R_BLK16, 0x1120
-	.set	R_BLK17, 0x1130
-	.set	R_BLK18, 0x1140
-	.set	R_BLK19, 0x1150
-	.set	R_BLK20, 0x1160
-	.set	R_BLK21, 0x1170
-	.set	R_BLK22, 0x1180
-	.set	R_BLK23, 0x1190
-	.set	R_BLK24, 0x11A0
-	.set	R_BLK25, 0x11B0
-	.set	R_BLK26, 0x11C0
-	.set	R_BLK27, 0x11D0
-	.set	R_BLK28, 0x11E0
-	.set	R_BLK29, 0x11F0
-	.set	R_BLK30, 0x1200
-	.set	R_BLK31, 0x1210
-	.set	R_BLK32, 0x1300
-	.set	R_BLK33, 0x1310
-	.set	R_BLK34, 0x1320
-	.set	R_BLK35, 0x1330
-	.set	R_BLK36, 0x1340
+	.set	SST_CAT0, 0x1210	# ...and one that starts empty
+	.set	SST_WORLD, 0x1220	# a source distinct from SST_HELLO
+	.set	R_BLK1, 0x1230
+	.set	R_BLK2, 0x1240
+	.set	R_BLK3, 0x1250
+	.set	R_BLK4, 0x1260
+	.set	R_BLK5, 0x1270
+	.set	R_BLK6, 0x1280
+	.set	R_BLK7, 0x1290
+	.set	R_BLK8, 0x12A0
+	.set	R_BLK9, 0x12B0
+	.set	R_BLK10, 0x12C0
+	.set	R_BLK11, 0x12D0
+	.set	R_BLK12, 0x12E0
+	.set	R_BLK13, 0x12F0
+	.set	R_BLK14, 0x1300
+	.set	R_BLK15, 0x1310
+	.set	R_BLK16, 0x1320
+	.set	R_BLK17, 0x1330
+	.set	R_BLK18, 0x1340
+	.set	R_BLK19, 0x1350
+	.set	R_BLK20, 0x1360
+	.set	R_BLK21, 0x1370
+	.set	R_BLK22, 0x1380
+	.set	R_BLK23, 0x1390
+	.set	R_BLK24, 0x13A0
+	.set	R_BLK25, 0x13B0
+	.set	R_BLK26, 0x13C0
+	.set	R_BLK27, 0x13D0
+	.set	R_BLK28, 0x13E0
+	.set	R_BLK29, 0x13F0
+	.set	R_BLK30, 0x1400
+	.set	R_BLK31, 0x1410
+	.set	R_BLK32, 0x1500
+	.set	R_BLK33, 0x1510
+	.set	R_BLK34, 0x1520
+	.set	R_BLK35, 0x1530
+	.set	R_BLK36, 0x1540
 
 	.code16
 	.text
@@ -192,7 +192,11 @@ drive:
 	# 2. Open the record: SOH, the case index as two hex digits, STX.
 	movb	$SOH, %al
 	call	putc
-	movb	OFF_CASE(%bp), %al
+	movw	OFF_CASE(%bp), %ax
+	movw	%ax, %bx
+	movb	%bh, %al
+	call	hexb
+	movb	%bl, %al
 	call	hexb
 	movb	$STX, %al
 	call	putc
@@ -216,7 +220,8 @@ drive:
 	movw	%ax, D_DUMPLEN + LOAD_BIAS
 	movw	OFF_DUMPADR(%bp), %ax
 	movw	%ax, D_DUMPADR + LOAD_BIAS
-	movw	OFF_NCALLS(%bp), %ax
+	xorw	%ax, %ax
+	movb	OFF_NCALLS(%bp), %al
 	movw	%ax, D_CNT + LOAD_BIAS
 	addw	$SLOT0, %bp		# BP now points AT the first call slot
 .Lcall:
@@ -269,6 +274,8 @@ drive:
 # The flags are re-read after the call because the entry has just used AX.
 doslot:
 	movb	4(%bp), %al
+	testb	$32, %al
+	jnz	.Lthreestack
 	testb	$8, %al
 	jnz	.Ltwostack
 	testb	$1, %al
@@ -278,6 +285,11 @@ doslot:
 .Ltwostack:
 	pushw	6(%bp)			# arg B: the source, ends at [BP+6]
 	pushw	2(%bp)			# arg A: the destination, ends at [BP+4]
+	jmp	.Lnostack
+.Lthreestack:
+	pushw	8(%bp)			# [BP+8]
+	pushw	6(%bp)			# [BP+6]
+	pushw	2(%bp)			# [BP+4]
 .Lnostack:
 	testb	$2, %al
 	jz	.Lnoax
@@ -294,6 +306,8 @@ doslot:
 	call	*%si
 	movw	%ax, D_AX + LOAD_BIAS	# observable return value
 	movb	4(%bp), %al
+	testb	$32, %al
+	jnz	.Lcleanthree
 	testb	$8, %al
 	jnz	.Lcleantwo
 	testb	$1, %al
@@ -304,6 +318,9 @@ doslot:
 .Lcleantwo:
 	addw	$4, %sp
 	ret
+.Lcleanthree:
+	addw	$6, %sp
+	ret
 
 # wrtinl's argument is NOT a stack word: the entry reads a length byte and that
 # many characters from its own return address.  That is the caller's half of

+ 6 - 3
shell/tests/fixtures/calc_ladder.tsv

@@ -28,6 +28,9 @@
 # Current rung (2026-10-21): `Write(Value:FW:DEC)' in CALC.INC's GotoCell - a
 # REAL write with a field width and decimals.  UpCase (t81_upcase), run-time
 # set membership (t82_setin) and a string field written through DI (t79
-# extended) moved the ladder 15911 -> 18267.  The blocker is now REAL
-# OUTPUT/ARITHMETIC: formatting a 6-byte MBF value with FW:DEC.
-CALC.PAS	ERR	102	18267
+# extended) moved the ladder 15911 -> 18267 -> 19847.  REAL output now works
+# (`Write(Value:FW:DEC)`, `Write(Value:FW)` and the default width), and a
+# string field may be compared before the right operand is parsed.  The blocker
+# is now run-time SET ARITHMETIC:
+#   until (Sheet[FX,FY].CellStatus * [OverWritten,Locked] = []) or ...;
+CALC.PAS	ERR	102	19847

+ 5 - 0
shell/tests/fixtures/expected.tsv

@@ -611,3 +611,8 @@ t81_upcase	OK	273	6
 #                   literal, a range literal, a typed constant and a data
 #                   set (a record field); the byte/bit index is computed.
 t82_setin	OK	1052	26
+
+#   t83/t84 are the real-output rung: a string field compared through DI,
+#   and Write(real:width:decimals) with both formats driven by variables.
+t83_strfield_cmp	OK	159	29
+t84_realwrite	OK	160	15

+ 2 - 0
shell/tests/fixtures/t83_strfield_cmp.out

@@ -0,0 +1,2 @@
+no
+empty

+ 17 - 0
shell/tests/fixtures/t83_strfield_cmp.pas

@@ -0,0 +1,17 @@
+PROGRAM T83;
+TYPE
+   Rec = RECORD
+      Contents : STRING [10] ;
+   END ;
+VAR
+   A : ARRAY [1..2] OF Rec ;
+   I : INTEGER ;
+BEGIN
+   I := 1 ;
+   IF A [I].Contents <> '' THEN
+      WRITELN ('yes')
+   ELSE
+      WRITELN ('no') ;
+   IF A [2].Contents = '' THEN
+      WRITELN ('empty')
+END.

+ 3 - 0
shell/tests/fixtures/t84_realwrite.out

@@ -0,0 +1,3 @@
+     1234.00
+ 1234.00
+1.23E+03

+ 20 - 0
shell/tests/fixtures/t84_realwrite.pas

@@ -0,0 +1,20 @@
+PROGRAM T84;
+TYPE
+   RRec = RECORD
+      R : Real ;
+   END ;
+VAR
+   X : RRec ;
+   W, D : Integer ;
+BEGIN
+   X.R := 1234 ;
+   W := 12 ; D := 2 ;
+   Write (X.R:W:D) ;
+   WriteLn ;
+   W := 8 ;
+   Write (X.R:W:D) ;
+   WriteLn ;
+   W := 8 ;
+   Write (X.R:W) ;
+   WriteLn
+END.

+ 58 - 0
shell/tests/nonvacuity.sh

@@ -2246,6 +2246,64 @@ else
 fi
 cp "$SAVED_C" Compiler.mod
 
+# --- real output goes through real_write, not the placeholder ----------
+# TU_WrReal is still the "?REAL?" marker; the compiler's new real arm must
+# select TU_RealWrite.  t84 prints a real field with variable width/decimals.
+cp "$SAVED_C" Compiler.mod
+if mutate_compiler 's|                        ent := TU_RealWrite ;|                        ent := TU_WrReal ;|'; then
+    if rebuild_compiler; then
+        expect_red "t84 red when real output falls back to the placeholder" \
+            "t84_realwrite" python3 tests/run_com_exec.py t84_realwrite
+    else
+        echo "  FAIL: the compiler would not rebuild with the placeholder"
+        fail=$((fail + 1))
+    fi
+fi
+cp "$SAVED_C" Compiler.mod
+
+# --- a DI-addressed string field is an address, not a scalar ------------
+# `if A[I].Contents <> '' then' must push DI; the old EmPushStrAddr refused
+# kind 5 (ENoLib) and SaveLeft would try to load the whole string.
+cp "$SAVED_C" Compiler.mod
+if python3 - <<'PYX'
+p = 'Compiler.mod'
+s = open(p).read()
+old = """      EmPushDi ()
+   ELSIF r.kind = 1 THEN"""
+assert s.count(old) == 1, 'the kind-5 string push found %d times' % s.count(old)
+open(p, 'w').write(s.replace(old, """      Err (ENoLib)
+   ELSIF r.kind = 1 THEN"""))
+PYX
+then
+    if rebuild_compiler; then
+        expect_red "t83 red when a string field address is refused" \
+            "t83_strfield_cmp" python3 tests/run_com_exec.py t83_strfield_cmp
+    else
+        echo "  FAIL: the compiler would not rebuild without the kind-5 push"
+        fail=$((fail + 1))
+    fi
+else
+    echo "  BROKEN CASE: the kind-5 string-field mutation did not apply"
+    fail=$((fail + 1))
+fi
+cp "$SAVED_C" Compiler.mod
+
+# --- real_write pads from the WIDTH, not from the length ----------------
+# The pad is width - length; using the length for both makes it zero, so every
+# right-justified field loses its leading spaces.  rt_exec checks exact output.
+cp "$SAVED" Runtime.mod
+if mutate Runtime.mod 's|   MovAxData (D_FMTW) ;|   MovAxData (D_FMTLEN) ;|'; then
+    if "$GM2" -fiso -c Runtime.mod >/dev/null 2>&1; then
+        expect_red "real_write red when the pad uses the length" \
+            "real_write" python3 tests/rt_exec.py
+    else
+        echo "  FAIL: the runtime would not rebuild with the pad mutation"
+        fail=$((fail + 1))
+    fi
+fi
+cp "$SAVED" Runtime.mod
+"$GM2" -fiso -c Runtime.mod >/dev/null 2>&1
+
 if rebuild_compiler; then
     if python3 tests/run_com_exec.py >/dev/null 2>&1; then
         echo "  ok: every executed fixture passes on the restored sources"

+ 66 - 44
shell/tests/rt_exec.py

@@ -63,25 +63,25 @@ SHELL = os.path.dirname(HERE)
 # two are written down and then CHECKED against each other (see build_image).
 # A restated constant that is never compared is a constant waiting to lie.
 RT_BASE = 0x0190
-HDR = 0x0F00
+HDR = 0x1100
 DLEN = 32
-DATA = 0x0F10
-STORE = 0x0F40
-WSTR = 0x0F50
-WSTR_EMPTY = 0x0F58
-CASE = 0x0F60
+DATA = 0x1110
+STORE = 0x1140
+WSTR = 0x1150
+WSTR_EMPTY = 0x1158
+CASE = 0x1160
 CASE_SZ = 64
-RDSTR_DST = 0x0FB0
-SST_HELLO = 0x0FC0
-SST_LONG = 0x0FD0
-SST_EMPTY = 0x0FE0
-SST_DST = 0x0FF0
-SST_CAT = 0x1000
-SST_CAT0 = 0x1010
-SST_WORLD = 0x1020
-AXOUT = 0x0FA6          # the driver stores the last call's AX here
-FMTDEST = 0x1230        # a 64-byte string for the real_fmt cases
-ASCSRC = 0x1270          # the ASCIIZ strings for real_val
+RDSTR_DST = 0x11B0
+SST_HELLO = 0x11C0
+SST_LONG = 0x11D0
+SST_EMPTY = 0x11E0
+SST_DST = 0x11F0
+SST_CAT = 0x1200
+SST_CAT0 = 0x1210
+SST_WORLD = 0x1220
+AXOUT = 0x11A6          # the driver stores the last call's AX here
+FMTDEST = 0x1430        # a 64-byte string for the real_fmt cases
+ASCSRC = 0x1470          # the ASCIIZ strings for real_val
 REALDST = 0x0FA8
 
 # A .COM is loaded at CS:0100, and every address in the image is an image
@@ -109,7 +109,8 @@ ENTS = ["initmem", "progend", "stackchk", "wrint", "wrchar", "wrbool",
         "wrreal", "wrln", "rdint", "rdchar", "rdbool", "rdln", "halt",
         "wrtinl", "rdbyte", "wrstr", "rdstr", "strstore", "cmpstr", "strcat",
         "wrintw", "real_from_int", "real_add", "real_sub", "real_mul",
-        "real_div", "real_cmp", "real_int", "real_fmt", "real_val"]
+        "real_div", "real_cmp", "real_int", "real_fmt", "real_val",
+        "real_write"]
 
 # A call slot's flags, as rtdrv.s reads them.
 F_STACK = 1     # the argument goes on the stack, popped by the caller
@@ -119,6 +120,8 @@ F_STACK2 = 8    # two stack words: +6 pushed first (lands at [BP+6]), +2 second
                 # (at [BP+4]) -- strstore's dest/src order
 F_CX = 16       # CX := the word at +8, the maximum length rdstr and strstore
                 # both read from the register
+F_STACK3 = 32   # three stack words: +8 first, then +6, then +2, so +2 is
+                # [BP+4], +6 is [BP+6] and +8 is [BP+8]
 
 # The byte patterns check_bp_contract looks for, and the rule they express:
 # "an entry that borrows BP opens with PUSH BP; MOV BP,SP and pops it again
@@ -315,12 +318,12 @@ def case_bytes(c, entries):
     struct.pack_into("<H", b, 2, c["dumpadr"])          # OFF_DUMPADR
     b[4] = len(c["in"])                                 # OFF_INLEN
     b[5:5 + len(c["in"])] = c["in"]                     # OFF_INBUF
-    b[13] = c["index"]                                  # OFF_CASE
+    struct.pack_into("<H", b, 13, c["index"])           # OFF_CASE (word)
     calls = c["calls"]
     if len(calls) > 4:
         sys.exit("case %s has %d calls; the record holds 4"
                  % (c["name"], len(calls)))
-    struct.pack_into("<H", b, 14, len(calls))           # OFF_NCALLS
+    b[15] = len(calls)                                  # OFF_NCALLS (byte)
     for i, (ent, arg, mode, arg2, cx) in enumerate(calls):
         o = 16 + 12 * i                                 # SLOT0, SLOT_SZ = 12
         struct.pack_into("<H", b, o, entries[ent])
@@ -387,20 +390,20 @@ def parse_record(raw, want_index):
     """
     if not raw or raw[0] != SOH:
         raise AssertionError("no record header: the driver printed %r" % raw[:64])
-    if len(raw) < 5 or raw[3] != STX:
+    if len(raw) < 7 or raw[5] != STX:
         raise AssertionError("malformed record header %r" % raw[:16])
-    idx = int(raw[1:3], 16)
+    idx = int(raw[1:5], 16)
     if idx != want_index:
-        raise AssertionError("record is for case %02X, expected %02X"
+        raise AssertionError("record is for case %04X, expected %04X"
                              % (idx, want_index))
     if raw[-1] != EOT:
         raise AssertionError("record was never closed (EOT): the last call did "
                              "not return, or the machine halted early")
-    end = raw.index(ETX, 4)
+    end = raw.index(ETX, 6)
     if end < 0:
         raise AssertionError("record has no ETX: the calls printed nothing and "
                              "the driver never got past them")
-    printed, dump = raw[4:end], raw[end + 1:-1]
+    printed, dump = raw[6:end], raw[end + 1:-1]
     if len(dump) % 2 or not re.fullmatch(b"[0-9A-F]*", dump):
         raise AssertionError("the memory report %r is not hex" % dump)
     return idx, printed, bytes.fromhex(dump.decode())
@@ -448,6 +451,11 @@ def build_cases():
         second (at [BP+4]).  real_from_int(src, dst) takes its source first."""
         return (ent, arg, F_STACK | F_STACK2, arg2, 0)
 
+    def s3(ent, a, b, c):
+        """Three stack words: c is pushed first (landing at [BP+8]), then b
+        ([BP+6]), then a ([BP+4])."""
+        return (ent, a, F_STACK | F_STACK3, b, c)
+
     def s2cx(ent, arg, arg2, cx):
         """Two stack words AND CX: arg2 is pushed first (it lands at [BP+6]),
         arg second (at [BP+4]), and CX carries the maximum length.  strstore is
@@ -524,7 +532,7 @@ def build_cases():
               (6, 2), (10, 2), (15, 3), (1000, 2), (-6, 2), (6, -2),
               (-6, -2), (0, 5), (5, 2), (42, 6)]
     for bi, (a, b) in enumerate(BLOCKS):
-        blk = 0x1030 + 16 * bi
+        blk = 0x1230 + 16 * bi
         add("real_add(%d,%d)" % (a, b),
             [s("real_add", blk + LOAD_BIAS)],
             dump=6, dumpbytes=mbf(a + b), dumpadr=blk,
@@ -557,7 +565,7 @@ def build_cases():
         if (a, b) in seen:
             continue
         seen.add((a, b))
-        blk = 0x1030 + 16 * bi
+        blk = 0x1230 + 16 * bi
         want = (a > b) - (a < b)
         add("real_cmp(%d,%d)" % (a, b),
             [s("real_cmp", blk + LOAD_BIAS)],
@@ -569,14 +577,14 @@ def build_cases():
             (-0.5, 0.5), (12345.75, 12345.5), (2.5, 0.0), (-2.5, 0.0),
             (0.99, 0.0), (1.9, 0.0)]
     for fi, (a, b) in enumerate(FRAC):
-        blk = 0x1170 + 16 * fi
+        blk = 0x1370 + 16 * fi
         want = (a > b) - (a < b)
         add("real_cmp(%g,%g)" % (a, b),
             [s("real_cmp", blk + LOAD_BIAS)],
             dump=2, dumpbytes=struct.pack("<H", want & 0xFFFF), dumpadr=AXOUT,
             note="fractional magnitudes: the mantissa decides")
     add("real_cmp(2^40,0)",
-        [s("real_cmp", 0x1210 + LOAD_BIAS)],
+        [s("real_cmp", 0x1410 + LOAD_BIAS)],
         dump=2, dumpbytes=struct.pack("<h", 1), dumpadr=AXOUT,
         note="the exponent alone decides")
 
@@ -586,20 +594,20 @@ def build_cases():
     # (TPSRC's xint pushes the six-byte result back).
     for bi, (a, b) in enumerate(BLOCKS):
         if a in (0, 1, -3, 32767):
-            blk = 0x1030 + 16 * bi
+            blk = 0x1230 + 16 * bi
             add("real_int(%d)" % a,
                 [s("real_int", blk + LOAD_BIAS)],
                 dump=6, dumpbytes=mbf(math.trunc(a)), dumpadr=blk,
                 note="the integer part as a real, the reference's reint")
     for fi, (a, b) in enumerate(FRAC):
-        blk = 0x1170 + 16 * fi
+        blk = 0x1370 + 16 * fi
         add("real_int(%g)" % a,
             [s("real_int", blk + LOAD_BIAS)],
             dump=6, dumpbytes=mbf(math.trunc(a)), dumpadr=blk,
             note="the fraction is masked away; the sign and exponent stay")
     add("real_int(2^40)",
-        [s("real_int", 0x1210 + LOAD_BIAS)],
-        dump=6, dumpbytes=bytes.fromhex("A90000000000"), dumpadr=0x1210,
+        [s("real_int", 0x1410 + LOAD_BIAS)],
+        dump=6, dumpbytes=bytes.fromhex("A90000000000"), dumpadr=0x1410,
         note="exponent >= 0A8h is already integral and returns unchanged")
 
     # --- fmtreal: real_fmt writes a length-prefixed, left-padded field -----
@@ -616,15 +624,15 @@ def build_cases():
          s2cx("real_fmt", STORE + LOAD_BIAS, FMTDEST + LOAD_BIAS, 0)],
         stdin=struct.pack("<h", 1), dump=2, dumpbytes=bytes([1]) + b"1",
         dumpadr=FMTDEST, note="a known-good 1.0 from real_from_int")
-    fmt("real_fmt(1:0:0)", 0x1030, 0, 0, b"1")
-    fmt("real_fmt(0:0:0)", 0x10B0, 0, 0, b"0")
-    fmt("real_fmt(3.7:0:2)", 0x1170, 0, 2, b"3.70")
-    fmt("real_fmt(12345.75:12:2)", 0x11C0, 12, 2, b"    12345.75")
-    fmt("real_fmt(0.5:6:3)", 0x11A0, 6, 3, b" 0.500")
-    fmt("real_fmt(-3.7:0:1)", 0x1190, 0, 1, b"-3.7")
-    fmt("real_fmt(0:5:2)", 0x10B0, 5, 2, b" 0.00")
-    fmt("real_fmt(2.5:0:0)", 0x11D0, 0, 0, b"3")
-    fmt("real_fmt(3.7:10:25)", 0x1170, 10, 25, b"3.7000E+00")
+    fmt("real_fmt(1:0:0)", 0x1230, 0, 0, b"1")
+    fmt("real_fmt(0:0:0)", 0x12B0, 0, 0, b"0")
+    fmt("real_fmt(3.7:0:2)", 0x1370, 0, 2, b"3.70")
+    fmt("real_fmt(12345.75:12:2)", 0x13C0, 12, 2, b"    12345.75")
+    fmt("real_fmt(0.5:6:3)", 0x13A0, 6, 3, b" 0.500")
+    fmt("real_fmt(-3.7:0:1)", 0x1390, 0, 1, b"-3.7")
+    fmt("real_fmt(0:5:2)", 0x12B0, 5, 2, b" 0.00")
+    fmt("real_fmt(2.5:0:0)", 0x13D0, 0, 0, b"3")
+    fmt("real_fmt(3.7:10:25)", 0x1370, 10, 25, b"3.7000E+00")
 
     # --- ascreal: real_val parses an ASCIIZ string ------------------------
     # A dump of AXOUT shows the returned offset, or FFFFh on a hard error.
@@ -655,17 +663,31 @@ def build_cases():
         off += len(txt)
 
     for bi, (a, b) in [(0, (12340, 5)), (1, (12340, 1))]:
-        blk = 0x1300 + 16 * bi
+        blk = 0x1500 + 16 * bi
         add("real_add(%d,%d)" % (a, b), [s("real_add", blk + LOAD_BIAS)],
             dump=6, dumpbytes=mbf(a + b), dumpadr=blk,
             note="a large value plus a small digit: the add core's alignment")
 
     for bi, a in [(2, 12341), (3, 12345), (4, 12340)]:
-        blk = 0x1300 + 16 * bi
+        blk = 0x1500 + 16 * bi
         add("real_mul(%d,1)" % a, [s("real_mul", blk + LOAD_BIAS)],
             dump=6, dumpbytes=mbf(a), dumpadr=blk,
             note="multiplying by 1.0 must be exact")
 
+    # --- real_write: [BP+4]=decimals, [BP+6]=width, [BP+8]=real ----------
+    def wr(name, blk, width, dec, text):
+        add(name, [s3("real_write", dec, width, blk + LOAD_BIAS)], out=text,
+            note="fmtreal's field, padded and printed by the entry itself")
+    wr("real_write(3.7:0:2)", 0x1370, 0, 2, b"3.70")
+    wr("real_write(3.7:10:2)", 0x1370, 10, 2, b"      3.70")
+    wr("real_write(12345.75:12:2)", 0x13C0, 12, 2, b"    12345.75")
+    wr("real_write(0.5:0:3)", 0x13A0, 0, 3, b"0.500")
+    wr("real_write(-3.7:0:1)", 0x1390, 0, 1, b"-3.7")
+    wr("real_write(2.5:0:0)", 0x13D0, 0, 0, b"3")
+    wr("real_write(42:0:0)", 0x1290, 0, 0, b"42")
+    wr("real_write(3.7:18:FFFF)", 0x1370, 18, 0xFFFF,
+       b"  3.7000000000E+00")
+
     add("wrchar('A')", [s("wrchar", ord("A"))], out=b"A")
     add("wrchar('!')", [s("wrchar", ord("!"))], out=b"!")
 

+ 129 - 84
shell/tests/runtime.golden

@@ -41,7 +41,7 @@
 0036  58          pop ax
 0037  F7 D8       neg ax
 0039  B9 0A 00    mov cx,0ah
-003C  BB 7A 0D    mov bx,0d7ah
+003C  BB DC 0D    mov bx,0ddch
 003F  89 DE       mov si,bx
 0041  31 D2       xor dx,dx
 0043  F7 F1       div ax,cx
@@ -93,7 +93,7 @@
 00A2  58          pop ax
 00A3  F7 D8       neg ax
 00A5  B9 0A 00    mov cx,0ah
-00A8  BB 7A 0D    mov bx,0d7ah
+00A8  BB DC 0D    mov bx,0ddch
 00AB  89 DE       mov si,bx
 00AD  31 D2       xor dx,dx
 00AF  F7 F1       div ax,cx
@@ -125,18 +125,18 @@
 00DE  83 7E 04 00 cmp word ptr [bp+4h],0h
 00E2  89 EC       mov sp,bp
 00E4  75 05       jne 0ebh
-00E6  BA 7F 0D    mov dx,0d7fh
+00E6  BA E1 0D    mov dx,0de1h
 00E9  EB 03       jmp 0eeh
-00EB  BA 7A 0D    mov dx,0d7ah
+00EB  BA DC 0D    mov dx,0ddch
 00EE  B4 09       mov ah,9h
 00F0  CD 21       int 21h
 00F2  5D          pop bp
 00F3  C3          ret
-00F4  BA 88 0D    mov dx,0d88h
+00F4  BA EA 0D    mov dx,0deah
 00F7  B4 09       mov ah,9h
 00F9  CD 21       int 21h
 00FB  C3          ret
-00FC  BA 85 0D    mov dx,0d85h
+00FC  BA E7 0D    mov dx,0de7h
 00FF  B4 09       mov ah,9h
 0101  CD 21       int 21h
 0103  C3          ret
@@ -303,12 +303,12 @@
 0217  EB EF       jmp 208h
 0219  58          pop ax
 021A  C3          ret
-021B  8B 1E 8E 0D mov bx,word ptr [0d8eh]
+021B  8B 1E F0 0D mov bx,word ptr [0df0h]
 021F  83 FB 00    cmp bx,0h
 0222  74 09       je 22dh
 0224  8A C3       mov al,bl
 0226  31 DB       xor bx,bx
-0228  89 1E 8E 0D mov word ptr [0d8eh],bx
+0228  89 1E F0 0D mov word ptr [0df0h],bx
 022C  C3          ret
 022D  B4 08       mov ah,8h
 022F  CD 21       int 21h
@@ -316,7 +316,7 @@
 0232  88 C2       mov dl,al
 0234  BB 01 00    mov bx,1h
 0237  8A DA       mov bl,dl
-0239  89 1E 8E 0D mov word ptr [0d8eh],bx
+0239  89 1E F0 0D mov word ptr [0df0h],bx
 023D  C3          ret
 023E  55          push bp
 023F  8B EC       mov bp,sp
@@ -543,7 +543,7 @@
 03C2  8B 4D 06    mov cx,word ptr [di+6h]
 03C5  8B 75 08    mov si,word ptr [di+8h]
 03C8  8B 7D 0A    mov di,word ptr [di+0ah]
-03CB  C7 06 90 0D 00 00 mov word ptr [0d90h],0h
+03CB  C7 06 F2 0D 00 00 mov word ptr [0df2h],0h
 03D1  E8 4D 00    call 421h
 03D4  5F          pop di
 03D5  89 05       mov word ptr [di],ax
@@ -572,7 +572,7 @@
 03FE  8B 4D 06    mov cx,word ptr [di+6h]
 0401  8B 75 08    mov si,word ptr [di+8h]
 0404  8B 7D 0A    mov di,word ptr [di+0ah]
-0407  C7 06 90 0D 00 80 mov word ptr [0d90h],8000h
+0407  C7 06 F2 0D 00 80 mov word ptr [0df2h],8000h
 040D  E8 11 00    call 421h
 0410  5F          pop di
 0411  89 05       mov word ptr [di],ax
@@ -587,7 +587,7 @@
 0420  C3          ret
 0421  80 F9 00    cmp cl,0h
 0424  74 0E       je 434h
-0426  33 3E 90 0D xor di,word ptr [0d90h]
+0426  33 3E F2 0D xor di,word ptr [0df2h]
 042A  3C 00       cmp al,0h
 042C  75 07       jne 435h
 042E  89 C8       mov ax,cx
@@ -599,16 +599,16 @@
 0439  91          xchg ax,cx
 043A  87 F3       xchg bx,si
 043C  87 FA       xchg dx,di
-043E  88 0E 94 0D mov byte ptr [0d94h],cl
+043E  88 0E F6 0D mov byte ptr [0df6h],cl
 0442  28 C1       sub cl,al
 0444  80 F9 28    cmp cl,28h
 0447  72 06       jb 44fh
-0449  8A 0E 94 0D mov cl,byte ptr [0d94h]
+0449  8A 0E F6 0D mov cl,byte ptr [0df6h]
 044D  EB DF       jmp 42eh
-044F  89 3E 90 0D mov word ptr [0d90h],di
-0453  80 26 91 0D 80 and byte ptr [0d91h],80h
-0458  89 3E 92 0D mov word ptr [0d92h],di
-045C  30 36 93 0D xor byte ptr [0d93h],dh
+044F  89 3E F2 0D mov word ptr [0df2h],di
+0453  80 26 F3 0D 80 and byte ptr [0df3h],80h
+0458  89 3E F4 0D mov word ptr [0df4h],di
+045C  30 36 F5 0D xor byte ptr [0df5h],dh
 0460  81 CF 00 80 or di,8000h
 0464  80 CE 80    or dh,80h
 0467  80 F9 10    cmp cl,10h
@@ -633,8 +633,8 @@
 0492  D0 DC       rcr ah,1h
 0494  FE C9       dec cl
 0496  75 F6       jne 48eh
-0498  A0 94 0D    mov al,byte ptr [0d94h]
-049B  F6 06 93 0D 80 test byte ptr [0d93h],80h
+0498  A0 F6 0D    mov al,byte ptr [0df6h]
+049B  F6 06 F5 0D 80 test byte ptr [0df5h],80h
 04A0  75 14       jne 4b6h
 04A2  00 EC       add ah,ch
 04A4  11 F3       adc bx,si
@@ -654,7 +654,7 @@
 04BE  19 F3       sbb bx,si
 04C0  19 FA       sbb dx,di
 04C2  73 14       jnb 4d8h
-04C4  80 36 91 0D 80 xor byte ptr [0d91h],80h
+04C4  80 36 F3 0D 80 xor byte ptr [0df3h],80h
 04C9  F6 D4       not ah
 04CB  F7 D3       not bx
 04CD  F7 D2       not dx
@@ -686,7 +686,7 @@
 0506  31 D2       xor dx,dx
 0508  C3          ret
 0509  80 E6 7F    and dh,7fh
-050C  32 36 91 0D xor dh,byte ptr [0d91h]
+050C  32 36 F3 0D xor dh,byte ptr [0df3h]
 0510  C3          ret
 0511  55          push bp
 0512  8B EC       mov bp,sp
@@ -732,23 +732,23 @@
 0565  B0 01       mov al,1h
 0567  B4 4C       mov ah,4ch
 0569  CD 21       int 21h
-056B  89 0E 9B 0D mov word ptr [0d9bh],cx
+056B  89 0E FD 0D mov word ptr [0dfdh],cx
 056F  8B CA       mov cx,dx
 0571  31 F9       xor cx,di
 0573  F6 D5       not ch
 0575  80 E5 80    and ch,80h
-0578  88 2E 91 0D mov byte ptr [0d91h],ch
+0578  88 2E F3 0D mov byte ptr [0df3h],ch
 057C  80 CE 80    or dh,80h
 057F  81 CF 00 80 or di,8000h
-0583  89 36 9D 0D mov word ptr [0d9dh],si
-0587  89 3E 9F 0D mov word ptr [0d9fh],di
-058B  A3 95 0D    mov word ptr [0d95h],ax
-058E  89 1E 97 0D mov word ptr [0d97h],bx
-0592  89 16 99 0D mov word ptr [0d99h],dx
+0583  89 36 FF 0D mov word ptr [0dffh],si
+0587  89 3E 01 0E mov word ptr [0e01h],di
+058B  A3 F7 0D    mov word ptr [0df7h],ax
+058E  89 1E F9 0D mov word ptr [0df9h],bx
+0592  89 16 FB 0D mov word ptr [0dfbh],dx
 0596  30 E4       xor ah,ah
 0598  31 DB       xor bx,bx
 059A  31 D2       xor dx,dx
-059C  BF 9B 0D    mov di,0d9bh
+059C  BF FD 0D    mov di,0dfdh
 059F  B1 05       mov cl,5h
 05A1  47          inc di
 05A2  8A 2D       mov ch,byte ptr [di]
@@ -763,9 +763,9 @@
 05B5  BE 08 00    mov si,8h
 05B8  D0 DD       rcr ch,1h
 05BA  73 0C       jnb 5c8h
-05BC  02 26 96 0D add ah,byte ptr [0d96h]
-05C0  13 1E 97 0D adc bx,word ptr [0d97h]
-05C4  13 16 99 0D adc dx,word ptr [0d99h]
+05BC  02 26 F8 0D add ah,byte ptr [0df8h]
+05C0  13 1E F9 0D adc bx,word ptr [0df9h]
+05C4  13 16 FB 0D adc dx,word ptr [0dfbh]
 05C8  D1 DA       rcr dx,1h
 05CA  D1 DB       rcr bx,1h
 05CC  D0 DC       rcr ah,1h
@@ -785,7 +785,7 @@
 05E6  74 02       je 5eah
 05E8  FE C9       dec cl
 05EA  91          xchg ax,cx
-05EB  32 36 91 0D xor dh,byte ptr [0d91h]
+05EB  32 36 F3 0D xor dh,byte ptr [0df3h]
 05EF  3C 00       cmp al,0h
 05F1  75 06       jne 5f9h
 05F3  31 C0       xor ax,ax
@@ -834,29 +834,29 @@
 0647  B0 01       mov al,1h
 0649  B4 4C       mov ah,4ch
 064B  CD 21       int 21h
-064D  89 0E 9B 0D mov word ptr [0d9bh],cx
+064D  89 0E FD 0D mov word ptr [0dfdh],cx
 0651  8B CA       mov cx,dx
 0653  31 F9       xor cx,di
 0655  F6 D5       not ch
 0657  80 E5 80    and ch,80h
-065A  88 2E 91 0D mov byte ptr [0d91h],ch
+065A  88 2E F3 0D mov byte ptr [0df3h],ch
 065E  80 CE 80    or dh,80h
 0661  81 CF 00 80 or di,8000h
-0665  89 36 9D 0D mov word ptr [0d9dh],si
-0669  89 3E 9F 0D mov word ptr [0d9fh],di
-066D  A2 95 0D    mov byte ptr [0d95h],al
-0670  BF 9A 0D    mov di,0d9ah
+0665  89 36 FF 0D mov word ptr [0dffh],si
+0669  89 3E 01 0E mov word ptr [0e01h],di
+066D  A2 F7 0D    mov byte ptr [0df7h],al
+0670  BF FC 0D    mov di,0dfch
 0673  B1 05       mov cl,5h
 0675  BE 08 00    mov si,8h
-0678  3B 16 9F 0D cmp dx,word ptr [0d9fh]
+0678  3B 16 01 0E cmp dx,word ptr [0e01h]
 067C  75 0A       jne 688h
-067E  3B 1E 9D 0D cmp bx,word ptr [0d9dh]
+067E  3B 1E FF 0D cmp bx,word ptr [0dffh]
 0682  75 04       jne 688h
-0684  3A 26 9C 0D cmp ah,byte ptr [0d9ch]
+0684  3A 26 FE 0D cmp ah,byte ptr [0dfeh]
 0688  72 0C       jb 696h
-068A  2A 26 9C 0D sub ah,byte ptr [0d9ch]
-068E  1B 1E 9D 0D sbb bx,word ptr [0d9dh]
-0692  1B 16 9F 0D sbb dx,word ptr [0d9fh]
+068A  2A 26 FE 0D sub ah,byte ptr [0dfeh]
+068E  1B 1E FF 0D sbb bx,word ptr [0dffh]
+0692  1B 16 01 0E sbb dx,word ptr [0e01h]
 0696  F5          cmc
 0697  D0 D5       rcl ch,1h
 0699  4E          dec si
@@ -870,24 +870,24 @@
 06A8  D1 D3       rcl bx,1h
 06AA  D1 D2       rcl dx,1h
 06AC  73 CA       jnb 678h
-06AE  2A 26 9C 0D sub ah,byte ptr [0d9ch]
-06B2  1B 1E 9D 0D sbb bx,word ptr [0d9dh]
-06B6  1B 16 9F 0D sbb dx,word ptr [0d9fh]
+06AE  2A 26 FE 0D sub ah,byte ptr [0dfeh]
+06B2  1B 1E FF 0D sbb bx,word ptr [0dffh]
+06B6  1B 16 01 0E sbb dx,word ptr [0e01h]
 06BA  F8          clc
 06BB  EB D9       jmp 696h
 06BD  D0 E4       shl ah,1h
 06BF  D1 D3       rcl bx,1h
 06C1  D1 D2       rcl dx,1h
 06C3  72 11       jb 6d6h
-06C5  3B 16 9F 0D cmp dx,word ptr [0d9fh]
+06C5  3B 16 01 0E cmp dx,word ptr [0e01h]
 06C9  75 0A       jne 6d5h
-06CB  3B 1E 9D 0D cmp bx,word ptr [0d9dh]
+06CB  3B 1E FF 0D cmp bx,word ptr [0dffh]
 06CF  75 04       jne 6d5h
-06D1  3A 26 9C 0D cmp ah,byte ptr [0d9ch]
+06D1  3A 26 FE 0D cmp ah,byte ptr [0dfeh]
 06D5  F5          cmc
-06D6  8B 0E 95 0D mov cx,word ptr [0d95h]
-06DA  8B 1E 97 0D mov bx,word ptr [0d97h]
-06DE  8B 16 99 0D mov dx,word ptr [0d99h]
+06D6  8B 0E F7 0D mov cx,word ptr [0df7h]
+06DA  8B 1E F9 0D mov bx,word ptr [0df9h]
+06DE  8B 16 FB 0D mov dx,word ptr [0dfbh]
 06E2  9F          lahf
 06E3  F6 C6 80    test dh,80h
 06E6  75 09       jne 6f1h
@@ -1030,47 +1030,47 @@
 07EC  B5 00       mov ch,0h
 07EE  B6 00       mov dh,0h
 07F0  51          push cx
-07F1  BB EB 0D    mov bx,0debh
+07F1  BB 4D 0E    mov bx,0e4dh
 07F4  E8 41 02    call 0a38h
 07F7  59          pop cx
 07F8  89 D8       mov ax,bx
-07FA  BB EB 0D    mov bx,0debh
+07FA  BB 4D 0E    mov bx,0e4dh
 07FD  2B C3       sub ax,bx
-07FF  A3 2B 0E    mov word ptr [0e2bh],ax
-0802  89 0E 2D 0E mov word ptr [0e2dh],cx
-0806  8B 06 2D 0E mov ax,word ptr [0e2dh]
-080A  8B 16 2B 0E mov dx,word ptr [0e2bh]
+07FF  A3 8D 0E    mov word ptr [0e8dh],ax
+0802  89 0E 8F 0E mov word ptr [0e8fh],cx
+0806  8B 06 8F 0E mov ax,word ptr [0e8fh]
+080A  8B 16 8D 0E mov dx,word ptr [0e8dh]
 080E  2B C2       sub ax,dx
 0810  83 F8 00    cmp ax,0h
 0813  7D 02       jnl 817h
 0815  31 C0       xor ax,ax
-0817  A3 2F 0E    mov word ptr [0e2fh],ax
-081A  8B 16 2B 0E mov dx,word ptr [0e2bh]
+0817  A3 91 0E    mov word ptr [0e91h],ax
+081A  8B 16 8D 0E mov dx,word ptr [0e8dh]
 081E  03 C2       add ax,dx
 0820  3D FF 00    cmp ax,0ffh
 0823  77 03       jnbe 828h
 0825  E9 03 00    jmp 82bh
 0828  B8 FF 00    mov ax,0ffh
-082B  A3 31 0E    mov word ptr [0e31h],ax
+082B  A3 93 0E    mov word ptr [0e93h],ax
 082E  8B 56 06    mov dx,word ptr [bp+6h]
 0831  89 D7       mov di,dx
 0833  88 05       mov byte ptr [di],al
 0835  47          inc di
-0836  8B 0E 2F 0E mov cx,word ptr [0e2fh]
+0836  8B 0E 91 0E mov cx,word ptr [0e91h]
 083A  E3 07       jcxz 843h
 083C  B0 20       mov al,20h
 083E  88 05       mov byte ptr [di],al
 0840  47          inc di
 0841  E2 F9       loop 83ch
-0843  BE EB 0D    mov si,0debh
-0846  8B 0E 2B 0E mov cx,word ptr [0e2bh]
+0843  BE 4D 0E    mov si,0e4dh
+0846  8B 0E 8D 0E mov cx,word ptr [0e8dh]
 084A  E3 08       jcxz 854h
 084C  8A 04       mov al,byte ptr [si]
 084E  88 05       mov byte ptr [di],al
 0850  46          inc si
 0851  47          inc di
 0852  E2 F8       loop 84ch
-0854  8B 06 31 0E mov ax,word ptr [0e31h]
+0854  8B 06 93 0E mov ax,word ptr [0e93h]
 0858  5F          pop di
 0859  5E          pop si
 085A  5A          pop dx
@@ -1086,12 +1086,12 @@
 0866  57          push di
 0867  8B 5E 04    mov bx,word ptr [bp+4h]
 086A  8B 7E 06    mov di,word ptr [bp+6h]
-086D  89 1E 33 0E mov word ptr [0e33h],bx
+086D  89 1E 95 0E mov word ptr [0e95h],bx
 0871  E8 CF 02    call 0b43h
 0874  73 03       jnb 879h
 0876  E9 0B 00    jmp 884h
 0879  89 D8       mov ax,bx
-087B  8B 16 33 0E mov dx,word ptr [0e33h]
+087B  8B 16 95 0E mov dx,word ptr [0e95h]
 087F  2B C2       sub ax,dx
 0881  E9 03 00    jmp 887h
 0884  B8 FF FF    mov ax,0ffffh
@@ -1140,7 +1140,7 @@
 08CE  52          push dx
 08CF  53          push bx
 08D0  50          push ax
-08D1  88 0E AD 0D mov byte ptr [0dadh],cl
+08D1  88 0E 0F 0E mov byte ptr [0e0fh],cl
 08D5  80 F9 00    cmp cl,0h
 08D8  78 03       js 8ddh
 08DA  E9 02 00    jmp 8dfh
@@ -1151,7 +1151,7 @@
 08E6  D0 EB       shr bl,1h
 08E8  00 FB       add bl,bh
 08EA  30 FF       xor bh,bh
-08EC  8D BF AF 0D lea di,[bx+0dafh]
+08EC  8D BF 11 0E lea di,[bx+0e11h]
 08F0  8B 05       mov ax,word ptr [di]
 08F2  8B 5D 02    mov bx,word ptr [di+2h]
 08F5  8B 55 04    mov dx,word ptr [di+4h]
@@ -1168,7 +1168,7 @@
 0910  58          pop ax
 0911  5B          pop bx
 0912  5A          pop dx
-0913  F6 06 AD 0D 80 test byte ptr [0dadh],80h
+0913  F6 06 0F 0E 80 test byte ptr [0e0fh],80h
 0918  74 03       je 91dh
 091A  E9 03 00    jmp 920h
 091D  E9 27 FC    jmp 547h
@@ -1195,11 +1195,11 @@
 0947  3C 00       cmp al,0h
 0949  74 03       je 94eh
 094B  E9 16 00    jmp 964h
-094E  BE A1 0D    mov si,0da1h
+094E  BE 03 0E    mov si,0e03h
 0951  C7 04 30 30 mov word ptr [si],3030h
 0955  46          inc si
 0956  46          inc si
-0957  81 FE AD 0D cmp si,0dadh
+0957  81 FE 0F 0E cmp si,0e0fh
 095B  74 03       je 960h
 095D  E9 F1 FF    jmp 951h
 0960  B9 00 00    mov cx,0h
@@ -1243,7 +1243,7 @@
 09AF  FE C9       dec cl
 09B1  74 03       je 9b6h
 09B3  E9 F3 FF    jmp 9a9h
-09B6  BE A1 0D    mov si,0da1h
+09B6  BE 03 0E    mov si,0e03h
 09B9  88 F5       mov ch,dh
 09BB  B1 04       mov cl,4h
 09BD  D2 ED       shr ch,cl
@@ -1269,13 +1269,13 @@
 09E1  D1 D3       rcl bx,1h
 09E3  D1 D2       rcl dx,1h
 09E5  46          inc si
-09E6  81 FE AD 0D cmp si,0dadh
+09E6  81 FE 0F 0E cmp si,0e0fh
 09EA  74 03       je 9efh
 09EC  E9 CA FF    jmp 9b9h
 09EF  59          pop cx
 09F0  C3          ret
 09F1  30 E4       xor ah,ah
-09F3  BB A1 0D    mov bx,0da1h
+09F3  BB 03 0E    mov bx,0e03h
 09F6  03 D8       add bx,ax
 09F8  80 3F 35    cmp byte ptr [bx],35h
 09FB  C6 07 00    mov byte ptr [bx],0h
@@ -1346,7 +1346,7 @@
 0A92  00 C8       add al,cl
 0A94  78 03       js 0a99h
 0A96  E9 08 00    jmp 0aa1h
-0A99  C6 06 A1 0D 00 mov byte ptr [0da1h],0h
+0A99  C6 06 03 0E 00 mov byte ptr [0e03h],0h
 0A9E  E9 0C 00    jmp 0aadh
 0AA1  3C 0C       cmp al,0ch
 0AA3  73 03       jnb 0aa8h
@@ -1354,7 +1354,7 @@
 0AA8  B0 0B       mov al,0bh
 0AAA  E8 44 FF    call 9f1h
 0AAD  5B          pop bx
-0AAE  BE A1 0D    mov si,0da1h
+0AAE  BE 03 0E    mov si,0e03h
 0AB1  F6 C5 80    test ch,80h
 0AB4  75 03       jne 0ab9h
 0AB6  E9 05 00    jmp 0abeh
@@ -1440,7 +1440,7 @@
 0B75  31 DB       xor bx,bx
 0B77  31 D2       xor dx,dx
 0B79  31 C9       xor cx,cx
-0B7B  C6 06 AE 0D 00 mov byte ptr [0daeh],0h
+0B7B  C6 06 10 0E 00 mov byte ptr [0e10h],0h
 0B80  8A 0C       mov cl,byte ptr [si]
 0B82  80 F9 61    cmp cl,61h
 0B85  73 03       jnb 0b8ah
@@ -1469,7 +1469,7 @@
 0BB5  58          pop ax
 0BB6  5B          pop bx
 0BB7  5A          pop dx
-0BB8  C7 06 90 0D 00 00 mov word ptr [0d90h],0h
+0BB8  C7 06 F2 0D 00 00 mov word ptr [0df2h],0h
 0BBE  E8 60 F8    call 421h
 0BC1  59          pop cx
 0BC2  5E          pop si
@@ -1477,7 +1477,7 @@
 0BC4  F6 C5 40    test ch,40h
 0BC7  75 03       jne 0bcch
 0BC9  E9 04 00    jmp 0bd0h
-0BCC  FE 0E AE 0D dec byte ptr [0daeh]
+0BCC  FE 0E 10 0E dec byte ptr [0e10h]
 0BD0  46          inc si
 0BD1  E9 AC FF    jmp 0b80h
 0BD4  80 F9 2E    cmp cl,2eh
@@ -1489,7 +1489,7 @@
 0BE4  80 CD 40    or ch,40h
 0BE7  E9 E6 FF    jmp 0bd0h
 0BEA  80 F9 45    cmp cl,45h
-0BED  8A 0E AE 0D mov cl,byte ptr [0daeh]
+0BED  8A 0E 10 0E mov cl,byte ptr [0e10h]
 0BF1  74 03       je 0bf6h
 0BF3  E9 4D 00    jmp 0c43h
 0BF6  E8 2A FD    call 923h
@@ -1548,3 +1548,48 @@
 0C6B  E9 03 00    jmp 0c71h
 0C6E  80 E9 30    sub cl,30h
 0C71  C3          ret
+0C72  55          push bp
+0C73  8B EC       mov bp,sp
+0C75  53          push bx
+0C76  52          push dx
+0C77  56          push si
+0C78  57          push di
+0C79  8B 46 06    mov ax,word ptr [bp+6h]
+0C7C  A3 8F 0E    mov word ptr [0e8fh],ax
+0C7F  8B 56 04    mov dx,word ptr [bp+4h]
+0C82  8B 4E 06    mov cx,word ptr [bp+6h]
+0C85  8B 7E 08    mov di,word ptr [bp+8h]
+0C88  BB 4D 0E    mov bx,0e4dh
+0C8B  E8 AA FD    call 0a38h
+0C8E  89 D8       mov ax,bx
+0C90  BB 4D 0E    mov bx,0e4dh
+0C93  2B C3       sub ax,bx
+0C95  A3 8D 0E    mov word ptr [0e8dh],ax
+0C98  8B 06 8F 0E mov ax,word ptr [0e8fh]
+0C9C  8B 16 8D 0E mov dx,word ptr [0e8dh]
+0CA0  2B C2       sub ax,dx
+0CA2  83 F8 00    cmp ax,0h
+0CA5  7D 02       jnl 0ca9h
+0CA7  31 C0       xor ax,ax
+0CA9  A3 91 0E    mov word ptr [0e91h],ax
+0CAC  8B 0E 91 0E mov cx,word ptr [0e91h]
+0CB0  E3 08       jcxz 0cbah
+0CB2  B0 20       mov al,20h
+0CB4  B4 02       mov ah,2h
+0CB6  CD 21       int 21h
+0CB8  E2 F8       loop 0cb2h
+0CBA  BE 4D 0E    mov si,0e4dh
+0CBD  8B 0E 8D 0E mov cx,word ptr [0e8dh]
+0CC1  E3 09       jcxz 0ccch
+0CC3  8A 04       mov al,byte ptr [si]
+0CC5  B4 02       mov ah,2h
+0CC7  CD 21       int 21h
+0CC9  46          inc si
+0CCA  E2 F7       loop 0cc3h
+0CCC  5F          pop di
+0CCD  5E          pop si
+0CCE  5A          pop dx
+0CCF  5B          pop bx
+0CD0  89 EC       mov sp,bp
+0CD2  5D          pop bp
+0CD3  C3          ret