Skip to content

Commit 9946caf

Browse files
committed
printer: accurate line-printer mechanical test (PER->PRT + faithful display)
The SAT Printer Mechanical Test (printermechanicaltest.cap) now runs to its documented END OF DIAGNOSTIC HLT (PO=0x0BC0) and prints its real pattern sequence: solid 0x55 ('E') lines, inverse 0xAA ('-') lines, and the shifting graphic-set "barber-pole" exercising every character position. Model / batch: - sat_batches.c: the printer-mechanical batch enters at 0x0118 and synthesizes the required center card into the startup input buffer (0x0670-0x0675: integrated subsystem, no 2nd transport, normal drum/ribbon, with end-of-test HLT) since the scatter-image path has no physical center card. - printer.c: line-printer WRITE (cmd 0x42) shares the 6-byte order block with the typewriter PUT but parks at the b8 request-wait with RC00 set; it is released there and the transfer is armed on return to alpha (line_mode appends a paper break per WRITE). Control orders (single-space 0x2E -> 1 break, triple-space 0x5A -> 3 breaks) emit paper feeds; other control PERs complete silently. printer_begin_output() gains a line_mode argument. Display (wasm console): - main.c drain_printer(): emit the new tail AND clear the capture buffer each frame. Previously it only advanced a cursor, so out[] accumulated across frames, capped at sizeof(out)-1 within ~50 ms, and the panel froze mid-test (the mechanical test prints ~6.6M chars) — the apparent "hang at 0x014A". - ge.h: out[] 8192 -> 65536 for catch-up/fast-frame bursts. - console.html: drop the 80-column auto-wrap (white-space: pre, inline spans, no 80ch clamp) so the paper breaks lines only on the typewriter's own line feed and long lines scroll horizontally. Verified: halts at 0x0BC0, uniform ~244 cyc/WRITE (no stall); 279 unit tests pass.
1 parent 77138f6 commit 9946caf

8 files changed

Lines changed: 207 additions & 27 deletions

File tree

console/wasm/console.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -927,12 +927,18 @@
927927
border-radius: 3px; font: inherit; font-size: 8.5pt; padding: 0.1em 0.5em; cursor: pointer;
928928
}
929929
#tp_paper {
930-
height: 13em; overflow-y: auto;
930+
height: 13em; overflow: auto;
931931
padding: 0.6em 0.8em; margin: 0;
932932
background:
933933
repeating-linear-gradient(#f4f1e6, #f4f1e6 1.45em, #e7e3d2 1.45em, #e7e3d2 1.5em);
934934
color: #23241c; font-size: 10.5pt; line-height: 1.5em;
935-
white-space: pre-wrap; word-break: break-word;
935+
/* Continuous paper: break lines ONLY on the typewriter's own line feed (the
936+
* \n the printer emits per WRITE / spacing order) — no 80-column auto-wrap.
937+
* Long lines extend and scroll horizontally. */
938+
white-space: pre;
939+
}
940+
#tp_paper > span {
941+
display: inline;
936942
}
937943
#tp_paper .tp-prn { color: #1a1c14; }
938944
#tp_paper .tp-kbd { color: #6a4a1a; } /* operator-typed, ink-ribbon brown */

console/wasm/main.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,22 @@ EM_JS(void, printer_emit, (const char *text), {
4949
/* Bytes the machine has already printed and echoed to the chat panel. */
5050
static int printer_echoed = 0;
5151

52-
/* Push any newly-captured printer output to the JS chat transcript. */
52+
/* Push any newly-captured printer output to the JS chat transcript, then
53+
* RECLAIM the capture buffer. The JS side keeps its own scrolling transcript,
54+
* so the emulator-side out[] is only a per-frame staging buffer: if we merely
55+
* advanced a cursor (the old behaviour) out[] would accumulate across frames
56+
* and cap at sizeof(out)-1 within ~50 ms of real-time printing, after which
57+
* printer_capture_char drops everything and the panel freezes mid-test (the
58+
* line-printer mechanical test alone prints millions of characters). Emitting
59+
* the new tail and clearing each frame keeps the stream flowing without bound. */
5360
static void drain_printer(void) {
5461
int olen = printer_output_len(ge);
55-
if (olen <= printer_echoed)
56-
return;
57-
/* printer_output() is NUL-terminated; emit only the new tail. */
58-
printer_emit(printer_output(ge) + printer_echoed);
59-
printer_echoed = olen;
62+
if (olen > printer_echoed)
63+
printer_emit(printer_output(ge) + printer_echoed);
64+
if (olen > 0) {
65+
printer_output_clear(ge);
66+
printer_echoed = 0;
67+
}
6068
}
6169

6270
/* Feed one operator-keyboard byte (two-way chat input). Exposed to JS. */

ge.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,11 @@ struct ge {
604604
*/
605605
struct ge_integrated_printer {
606606
int present;
607-
char out[8192]; /* captured printed characters (ring/append) */
607+
char out[65536]; /* captured printed chars; per-frame staging only
608+
* (the wasm console drains+clears each frame, the
609+
* CLI reads it at end). Sized for a catch-up
610+
* frame's burst so fast/backgrounded printing
611+
* doesn't overflow before the drain. */
608612
int out_len;
609613
uint8_t kbd[256]; /* operator keyboard input queue */
610614
int kbd_head, kbd_tail;
@@ -617,6 +621,7 @@ struct ge {
617621
int out_active;
618622
int out_remaining;
619623
int out_total;
624+
int out_line_mode;
620625
uint8_t out_saved_so;
621626
} integrated_printer;
622627

printer.c

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,43 @@ struct printer_ctx {
6969

7070
#define STDIO_STATUS_ADDR 0x0030
7171
#define STDIO_COUNT_ADDR 0x0032
72+
#define LP_CMD_SINGLE_SPACE 0x2E
73+
#define LP_CMD_TRIPLE_SPACE 0x5A
7274

7375
static void store16(struct ge *ge, uint16_t addr, uint16_t value)
7476
{
7577
ge_mem_store8(ge, addr, (uint8_t)(value >> 8));
7678
ge_mem_store8(ge, (uint16_t)(addr + 1), (uint8_t)value);
7779
}
7880

81+
static void printer_capture_char(struct ge_integrated_printer *p, char c)
82+
{
83+
if (p->out_len >= (int)sizeof(p->out) - 1)
84+
return;
85+
p->out[p->out_len++] = c;
86+
p->out[p->out_len] = '\0';
87+
}
88+
89+
static void printer_capture_breaks(struct ge_integrated_printer *p, int count)
90+
{
91+
for (int i = 0; i < count; i++)
92+
printer_capture_char(p, '\n');
93+
}
94+
95+
static void printer_capture_control(struct ge_integrated_printer *p, uint8_t cmd)
96+
{
97+
switch (cmd) {
98+
case LP_CMD_SINGLE_SPACE:
99+
printer_capture_breaks(p, 1);
100+
break;
101+
case LP_CMD_TRIPLE_SPACE:
102+
printer_capture_breaks(p, 3);
103+
break;
104+
default:
105+
break;
106+
}
107+
}
108+
79109
static int kbd_ready(const struct ge_integrated_printer *p)
80110
{
81111
return p->kbd_head != p->kbd_tail;
@@ -188,9 +218,12 @@ static int printer_on_clock(struct ge *ge, void *opaque)
188218
* the stolen cycles clobbered, and end the line. */
189219
ge->RC02 = 0;
190220
ge->rSO = p->out_saved_so;
221+
if (p->out_line_mode)
222+
printer_capture_breaks(p, 1);
191223
store16(ge, STDIO_COUNT_ADDR, (uint16_t)p->out_total);
192224
store16(ge, STDIO_STATUS_ADDR, 1);
193225
p->out_active = 0;
226+
p->out_line_mode = 0;
194227
}
195228
return 0;
196229
}
@@ -217,7 +250,7 @@ static int printer_on_clock(struct ge *ge, void *opaque)
217250
ge->mem[(uint16_t)(base + 5)];
218251
ctx->per_pending = 0;
219252
if (IS_OUTPUT_CMD(cmd) && len >= 1 && len <= PRINT_LEN_MAX)
220-
printer_begin_output(ge, buf, len);
253+
printer_begin_output(ge, buf, len, cmd == LP_CMD_WRITE);
221254
else if (cmd == KBD_CMD_LINE && len >= 1 && len <= PRINT_LEN_MAX)
222255
service_input_line(ge, buf, len);
223256
else if (cmd == KBD_CMD_CHAR && len >= 1 && len <= PRINT_LEN_MAX)
@@ -250,6 +283,8 @@ static int printer_on_clock(struct ge *ge, void *opaque)
250283
cmd != KBD_CMD_CHAR &&
251284
!write_ready;
252285
if (write_ready || input_ready || control_ready) {
286+
if (control_ready)
287+
printer_capture_control(p, cmd);
253288
ge->PUC2 = 1; /* channel-2 unit ready -> DU97 completes the PER */
254289
ge->RC00 = 1; /* CPU-active request -> rSO=b8 routed into rSA */
255290
ctx->stall = 0;
@@ -281,6 +316,10 @@ static int printer_on_clock(struct ge *ge, void *opaque)
281316
* such as funktionalcpu's banner/report. Complete it via the native path so
282317
* the CPU resumes, but emit nothing: real printed text comes only from an
283318
* armed output transfer, not from heuristically scraping the order block. */
319+
if (ctx->per_pending) {
320+
uint8_t cmd = ge->mem[(uint16_t)(ctx->per_base + 1)];
321+
printer_capture_control(p, cmd);
322+
}
284323
ge->PUC2 = 1; /* channel-2 unit ready -> DU97 = 1 (PUC2 ^ L2.3) */
285324
ge->RC00 = 1; /* CPU-active request -> NA_knot routes rSO=b8 into rSA */
286325
ctx->stall = 0; /* one-shot; rSO leaves b8 next cycle */
@@ -304,9 +343,7 @@ static void printer_sink(struct ge *ge, struct ge_channel *ch, uint8_t c)
304343
struct ge_integrated_printer *p = &ge->integrated_printer;
305344
(void)ch;
306345

307-
if (p->out_len < (int)sizeof(p->out) - 1)
308-
p->out[p->out_len++] = ge_glyph(c);
309-
p->out[p->out_len] = '\0';
346+
printer_capture_char(p, ge_glyph(c));
310347
}
311348

312349
int printer_register(struct ge *ge)
@@ -333,12 +370,13 @@ int printer_register(struct ge *ge)
333370
return ge_register_peri(ge, &ctx->peri);
334371
}
335372

336-
void printer_begin_output(struct ge *ge, uint16_t buffer, int length)
373+
void printer_begin_output(struct ge *ge, uint16_t buffer, int length, int line_mode)
337374
{
338375
ge->rV4 = buffer;
339376
ge->integrated_printer.out_active = 1;
340377
ge->integrated_printer.out_remaining = length;
341378
ge->integrated_printer.out_total = length;
379+
ge->integrated_printer.out_line_mode = line_mode;
342380
ge->integrated_printer.out_saved_so = ge->rSO; /* resume here when done */
343381
}
344382

printer.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ int printer_register(struct ge *ge);
3636
/* Begin a channel-2 OUTPUT transfer: print `length` characters starting at
3737
* memory address `buffer`. The printer then requests a channel-2 cycle per
3838
* character (RC02 + rSI state 0x02) so the machine's microcode drains the buffer
39-
* to the printer, ending the line when done. (Seam for the org-phase PER hook;
40-
* also used directly by tests.) */
41-
void printer_begin_output(struct ge *ge, uint16_t buffer, int length);
39+
* to the printer. `line_mode` appends a paper break when the transfer ends;
40+
* this is used by the line printer's WRITE order but not by the console
41+
* typewriter PUT path. (Seam for the org-phase PER hook; also used directly by
42+
* tests.) */
43+
void printer_begin_output(struct ge *ge, uint16_t buffer, int length, int line_mode);
4244

4345
/* Push one operator-keyboard byte into the input queue (two-way chat). */
4446
void printer_feed_key(struct ge *ge, uint8_t c);

sat_batches.c

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ struct sat_batch_def {
2222
struct sat_batch_info info;
2323
const struct sat_source *sources;
2424
int nsources;
25+
uint16_t image_entry;
26+
};
27+
28+
struct sat_image_patch {
29+
uint16_t addr;
30+
uint8_t value;
2531
};
2632

2733
static const struct sat_source src_cpu_functional[] = {
@@ -36,6 +42,30 @@ static const struct sat_source src_printer_mech[] = {
3642
{ "printermechanicaltest.cap", SAT_SRC_AS_IS },
3743
};
3844

45+
/* Printer Mechanic Test startup: the deck issues PER 0x00,0x0126, whose order
46+
* block is {00,40,00,4F,06,70} — a 79-byte "read unchanged" into 0x0670.
47+
* In the real SAT stack that record comes from the required center card. The
48+
* current scatter-image path has no physical center card, so synthesize the
49+
* default raw reader bytes the diagnostic expects:
50+
* col1 integrated, col2 no 2nd transport, col4 normal drum, col5 normal
51+
* ribbon, col6 WITH END OF TEST HLT. The remaining bytes are left zero.
52+
*
53+
* Confidence:
54+
* - 0x0673 / 0x0674 are directly tested by the deck as 0x01 (normal drum /
55+
* ribbon path).
56+
* - 0x0675 follows the manual's center-card column 6. Under the raw-reader
57+
* low-byte model, a 9-punch collapses to 0x00, so WITH END OF TEST HLT is
58+
* represented here as 0x00.
59+
* - columns not yet evidenced by the code remain zero until more manual
60+
* evidence is recovered. */
61+
static const struct sat_image_patch printer_mech_center_card[] = {
62+
{ 0x0670, 0x01 }, /* column 1: integrated subsystem */
63+
{ 0x0671, 0x01 }, /* column 2: 2nd transport absent */
64+
{ 0x0673, 0x01 }, /* column 4: normal drum */
65+
{ 0x0674, 0x01 }, /* column 5: normal ribbon */
66+
{ 0x0675, 0x00 }, /* column 6: with END OF TEST HLT */
67+
};
68+
3969
static const struct sat_source src_control_program[] = {
4070
{ "control-program-cr.cap", SAT_SRC_SERIAL_LOADER_PLUS_BODY },
4171
};
@@ -60,42 +90,50 @@ static const struct sat_batch_def sat_batches[] = {
6090
{ "cpu-functional", "CPU Functional Test",
6191
"SAT step 1: the functional CPU deck staged through the scatter-image path.",
6292
SAT_BATCH_IMAGE },
63-
src_cpu_functional, (int)(sizeof(src_cpu_functional) / sizeof(src_cpu_functional[0]))
93+
src_cpu_functional, (int)(sizeof(src_cpu_functional) / sizeof(src_cpu_functional[0])),
94+
0x0000
6495
},
6596
{
6697
{ "card-reader-a", "Reading Test Channel A",
6798
"SAT step 2: the captured card-reader test deck.", SAT_BATCH_IMAGE },
68-
src_reader_a, (int)(sizeof(src_reader_a) / sizeof(src_reader_a[0]))
99+
src_reader_a, (int)(sizeof(src_reader_a) / sizeof(src_reader_a[0])),
100+
0x0000
69101
},
70102
{
71103
{ "printer-mechanical", "Printer Mechanical Test",
72-
"SAT step 3: the line-printer deck staged through the scatter-image path.",
104+
"SAT step 3: the line-printer deck staged through the scatter-image path, "
105+
"with the required center card synthesized into the startup input buffer.",
73106
SAT_BATCH_IMAGE },
74-
src_printer_mech, (int)(sizeof(src_printer_mech) / sizeof(src_printer_mech[0]))
107+
src_printer_mech, (int)(sizeof(src_printer_mech) / sizeof(src_printer_mech[0])),
108+
0x0118
75109
},
76110
{
77111
{ "control-program-cr", "Control Program CR",
78112
"Control-program utility deck with the serial Hollerith loader kept.",
79113
SAT_BATCH_READER },
80-
src_control_program, (int)(sizeof(src_control_program) / sizeof(src_control_program[0]))
114+
src_control_program, (int)(sizeof(src_control_program) / sizeof(src_control_program[0])),
115+
0x0000
81116
},
82117
{
83118
{ "ls600-controller-sat", "LS600 Controller SAT Batch",
84119
"Sequencer Program followed by LS600 Controller Test, prepared per the SAT notes.",
85120
SAT_BATCH_READER },
86-
src_ls600_controller, (int)(sizeof(src_ls600_controller) / sizeof(src_ls600_controller[0]))
121+
src_ls600_controller, (int)(sizeof(src_ls600_controller) / sizeof(src_ls600_controller[0])),
122+
0x0000
87123
},
88124
{
89125
{ "ls600-transcoder-sat", "LS600 Transcoder SAT Batch",
90126
"Sequencer Program followed by LS600 Transcoder Test, prepared per the SAT notes.",
91127
SAT_BATCH_READER },
92-
src_ls600_transcoder, (int)(sizeof(src_ls600_transcoder) / sizeof(src_ls600_transcoder[0]))
128+
src_ls600_transcoder, (int)(sizeof(src_ls600_transcoder) / sizeof(src_ls600_transcoder[0])),
129+
0x0000
93130
},
94131
{
95132
{ "ls600-doe-sat", "LS600 D.O.E. SAT Batch",
96133
"Sequencer Program followed by the LS600 D.O.E. deck, prepared per the SAT notes.",
97134
SAT_BATCH_READER },
98-
src_ls600_doe, (int)(sizeof(src_ls600_doe) / sizeof(src_ls600_doe[0]))
135+
src_ls600_doe, (int)(sizeof(src_ls600_doe) / sizeof(src_ls600_doe[0])),
136+
0x0000
99137
},
100138
};
101139

@@ -174,6 +212,20 @@ static const struct sat_batch_def *sat_batch_def_find(const char *id)
174212
return NULL;
175213
}
176214

215+
static void sat_apply_image_patches(const struct sat_batch_def *def, unsigned char *image)
216+
{
217+
const struct sat_image_patch *patches = NULL;
218+
size_t npatches = 0;
219+
220+
if (strcmp(def->info.id, "printer-mechanical") == 0) {
221+
patches = printer_mech_center_card;
222+
npatches = sizeof(printer_mech_center_card) / sizeof(printer_mech_center_card[0]);
223+
}
224+
225+
for (size_t i = 0; i < npatches; i++)
226+
image[patches[i].addr] = patches[i].value;
227+
}
228+
177229
static void sat_note(char *note, size_t note_sz, const struct sat_batch_def *def)
178230
{
179231
if (!note || note_sz == 0)
@@ -218,7 +270,8 @@ int sat_batch_prepare_image(const char *root, const char *id,
218270
if (rc < 0)
219271
return -1;
220272

221-
*entry = (uint16_t)*lo;
273+
sat_apply_image_patches(def, image);
274+
*entry = def->image_entry ? def->image_entry : (uint16_t)*lo;
222275
sat_note(note, note_sz, def);
223276
return 0;
224277
}

tests/printer.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ UTEST(printer, channel2_output_driven)
224224
g.mem_written[buf + 1] = 1;
225225

226226
ge_start(&g);
227-
printer_begin_output(&g, buf, 2);
227+
printer_begin_output(&g, buf, 2, 0);
228228

229229
/* Run enough cycles for the 2 transfers + the end cycle; it self-terminates. */
230230
for (int i = 0; i < 6; i++)
@@ -326,6 +326,32 @@ UTEST(printer, output_per_prints_and_halts_when_polled)
326326
ge_deinit(&g);
327327
}
328328

329+
UTEST(printer, line_printer_write_ends_with_newline)
330+
{
331+
struct ge g;
332+
uint16_t buf = 0x0200;
333+
ge_init(&g);
334+
335+
g.mem[buf + 0] = 0x55; /* E */
336+
g.mem[buf + 1] = 0x55; /* E */
337+
338+
ge_clear(&g);
339+
printer_register(&g);
340+
printer_begin_output(&g, buf, 2, 1);
341+
342+
for (int i = 0; i < 80; i++) {
343+
if (ge_run_cycle(&g))
344+
break;
345+
}
346+
347+
ASSERT_EQ(printer_output_len(&g), 3);
348+
ASSERT_EQ(printer_output(&g)[0], 'E');
349+
ASSERT_EQ(printer_output(&g)[1], 'E');
350+
ASSERT_EQ(printer_output(&g)[2], '\n');
351+
352+
ge_deinit(&g);
353+
}
354+
329355
UTEST(printer, input_line_waits_for_keyboard_and_fills_buffer)
330356
{
331357
struct ge g;

0 commit comments

Comments
 (0)