rk86 2.0.29 → 2.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/package.json +2 -2
  3. package/rk86.js +212 -25
package/README.md CHANGED
@@ -31,6 +31,8 @@ bunx rk86 --online CHESS.GAM # открыть файл в онлайн-э
31
31
  --memory-to <адрес> конец области дампа памяти включительно (по умолчанию: 0xFFFF)
32
32
  --screen <файл> сохранить экран 78x30 как текст при выходе
33
33
  --input <seq> инъекция клавиш (через запятую): KeyA,Digit1,Enter,...
34
+ --color <0|1|2|3> режим цвета: 0=ч/б (по умолчанию), 1=Толкалин,
35
+ 2=Акименко, 3=Апогей
34
36
  --online открыть в онлайн-эмуляторе rk86.ru
35
37
  ```
36
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rk86",
3
- "version": "2.0.29",
3
+ "version": "2.0.31",
4
4
  "description": "Эмулятор Радио-86РК (Intel 8080) для терминала",
5
5
  "bin": {
6
6
  "rk86": "rk86.js"
@@ -18,6 +18,6 @@
18
18
  "license": "MIT",
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "https://github.com/begoon/rk86-js-web"
21
+ "url": "https://github.com/begoon/rk86-js"
22
22
  }
23
23
  }
package/rk86.js CHANGED
@@ -165,6 +165,7 @@ function preprocess(source) {
165
165
  const out = [];
166
166
  const stack = [];
167
167
  let counter = 0;
168
+ let procCounter = 0;
168
169
  let proc = null;
169
170
  for (let i = 0;i < lines.length; i++) {
170
171
  const line = lines[i];
@@ -223,7 +224,14 @@ function preprocess(source) {
223
224
  regs.push(up);
224
225
  }
225
226
  }
226
- proc = { regs, line: orig, source: line };
227
+ const id = procCounter++;
228
+ proc = {
229
+ regs,
230
+ line: orig,
231
+ source: line,
232
+ exitLabel: `__proc_${id}_exit`,
233
+ returnUsed: false
234
+ };
227
235
  out.push({ text: `${name}:`, orig });
228
236
  for (const r of regs) {
229
237
  out.push({ text: ` PUSH ${r}`, orig });
@@ -237,6 +245,9 @@ function preprocess(source) {
237
245
  if (!proc) {
238
246
  throw new AsmError(".endp without .proc", orig, line, firstNonSpaceCol(line));
239
247
  }
248
+ if (proc.returnUsed) {
249
+ out.push({ text: `${proc.exitLabel}:`, orig });
250
+ }
240
251
  out.push(...popsAndRet(proc.regs, orig));
241
252
  proc = null;
242
253
  continue;
@@ -245,7 +256,12 @@ function preprocess(source) {
245
256
  if (!proc) {
246
257
  throw new AsmError(".return outside .proc", orig, line, firstNonSpaceCol(line));
247
258
  }
248
- out.push(...popsAndRet(proc.regs, orig));
259
+ if (proc.regs.length === 0) {
260
+ out.push({ text: ` RET`, orig });
261
+ } else {
262
+ proc.returnUsed = true;
263
+ out.push({ text: ` JMP ${proc.exitLabel}`, orig });
264
+ }
249
265
  continue;
250
266
  }
251
267
  out.push({ text: line, orig });
@@ -742,6 +758,7 @@ function countDb(operands) {
742
758
  function asm(source) {
743
759
  const pp = preprocess(source);
744
760
  const symbols = new Map;
761
+ const pending = [];
745
762
  let pc = 0;
746
763
  let lastLabel = "";
747
764
  let ended = false;
@@ -760,7 +777,7 @@ function asm(source) {
760
777
  lastLabel = parts.label;
761
778
  }
762
779
  if (parts.isEqu) {
763
- symbols.set(labelName.toUpperCase(), evalExpr(parts.operands[0], symbols, pc, lastLabel));
780
+ tryDefineEqu(symbols, pending, labelName, parts.operands[0], pc, lastLabel, orig, line);
764
781
  continue;
765
782
  }
766
783
  symbols.set(labelName.toUpperCase(), pc);
@@ -800,6 +817,7 @@ function asm(source) {
800
817
  throw new AsmError(e.message, orig, line, firstNonSpaceCol(line));
801
818
  }
802
819
  }
820
+ resolvePendingEqus(symbols, pending);
803
821
  const sections = [];
804
822
  let current = null;
805
823
  const sectionNames = new Set;
@@ -861,6 +879,50 @@ function asm(source) {
861
879
  }
862
880
  return sections;
863
881
  }
882
+ function isUnknownSymbolErr(e) {
883
+ return e instanceof Error && /^unknown symbol:/.test(e.message);
884
+ }
885
+ function tryDefineEqu(symbols, pending, name, expr, pc, lastLabel, orig, line) {
886
+ try {
887
+ symbols.set(name.toUpperCase(), evalExpr(expr, symbols, pc, lastLabel));
888
+ } catch (e) {
889
+ if (isUnknownSymbolErr(e)) {
890
+ pending.push({ name, expr, pc, lastLabel, orig, line });
891
+ } else {
892
+ throw e;
893
+ }
894
+ }
895
+ }
896
+ function resolvePendingEqus(symbols, pending) {
897
+ while (pending.length > 0) {
898
+ let progress = false;
899
+ const next = [];
900
+ for (const p of pending) {
901
+ try {
902
+ symbols.set(p.name.toUpperCase(), evalExpr(p.expr, symbols, p.pc, p.lastLabel));
903
+ progress = true;
904
+ } catch (e) {
905
+ if (isUnknownSymbolErr(e)) {
906
+ next.push(p);
907
+ } else {
908
+ throw new AsmError(e.message, p.orig, p.line, firstNonSpaceCol(p.line));
909
+ }
910
+ }
911
+ }
912
+ if (!progress) {
913
+ const p = next[0];
914
+ try {
915
+ evalExpr(p.expr, symbols, p.pc, p.lastLabel);
916
+ } catch (e) {
917
+ throw new AsmError(e.message, p.orig, p.line, firstNonSpaceCol(p.line));
918
+ }
919
+ return;
920
+ }
921
+ pending.length = 0;
922
+ pending.push(...next);
923
+ }
924
+ }
925
+ var DATA_DIRECTIVES = new Set(["DB", "DW", "DS"]);
864
926
  if (false) {}
865
927
 
866
928
  // src/lib/terminal/rk86_terminal.ts
@@ -871,7 +933,7 @@ import { basename } from "path";
871
933
  // packages/rk86/package.json
872
934
  var package_default = {
873
935
  name: "rk86",
874
- version: "2.0.28",
936
+ version: "2.0.30",
875
937
  description: "\u042D\u043C\u0443\u043B\u044F\u0442\u043E\u0440 \u0420\u0430\u0434\u0438\u043E-86\u0420\u041A (Intel 8080) \u0434\u043B\u044F \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430",
876
938
  bin: {
877
939
  rk86: "rk86.js"
@@ -889,10 +951,63 @@ var package_default = {
889
951
  license: "MIT",
890
952
  repository: {
891
953
  type: "git",
892
- url: "https://github.com/begoon/rk86-js-web"
954
+ url: "https://github.com/begoon/rk86-js"
893
955
  }
894
956
  };
895
957
 
958
+ // src/lib/core/rk86_colors.ts
959
+ var COLOR_MODES = ["mono", "color1", "color2", "color3"];
960
+ var DEFAULT_COLOR_MODE = "color1";
961
+ function attrToRgb(mode, attrs) {
962
+ const hglt = (attrs & 1) !== 0;
963
+ const gpa0 = (attrs & 4) !== 0;
964
+ const gpa1 = (attrs & 8) !== 0;
965
+ switch (mode) {
966
+ case "color1": {
967
+ const rgb = (gpa1 ? 255 : 0) | (gpa0 ? 65280 : 0) | (hglt ? 16711680 : 0);
968
+ return rgb === 0 ? 12632256 : rgb;
969
+ }
970
+ case "color2":
971
+ return (gpa0 ? 0 : 16711680) | (gpa1 ? 0 : 65280) | (hglt ? 0 : 255);
972
+ case "color3":
973
+ return (gpa0 ? 0 : 255) | (gpa1 ? 0 : 65280) | (hglt ? 0 : 16711680);
974
+ case "mono":
975
+ default:
976
+ return 12632256;
977
+ }
978
+ }
979
+ function hasCellOffset(mode) {
980
+ return mode !== "color3";
981
+ }
982
+ function rgbToAnsiBaseFg(rgb) {
983
+ const r = rgb >> 16 & 255;
984
+ const g = rgb >> 8 & 255;
985
+ const b = rgb & 255;
986
+ const palette = [
987
+ [30, 0, 0, 0],
988
+ [31, 255, 0, 0],
989
+ [32, 0, 255, 0],
990
+ [33, 255, 255, 0],
991
+ [34, 0, 0, 255],
992
+ [35, 255, 0, 255],
993
+ [36, 0, 255, 255],
994
+ [37, 255, 255, 255]
995
+ ];
996
+ let best = 37;
997
+ let bestDist = Infinity;
998
+ for (const [code, pr, pg, pb] of palette) {
999
+ const dr = r - pr;
1000
+ const dg = g - pg;
1001
+ const db = b - pb;
1002
+ const dist = dr * dr + dg * dg + db * db;
1003
+ if (dist < bestDist) {
1004
+ bestDist = dist;
1005
+ best = code;
1006
+ }
1007
+ }
1008
+ return best;
1009
+ }
1010
+
896
1011
  // src/lib/core/hex.ts
897
1012
  function hex(v, prefix) {
898
1013
  return v.toString(16).toUpperCase();
@@ -2208,6 +2323,8 @@ class Memory {
2208
2323
  return;
2209
2324
  }
2210
2325
  if (vg75_reg === 49152 && this.vg75_c001_00_cmd === 3) {
2326
+ this.machine.screen.set_char_height((byte & 15) + 1);
2327
+ this.machine.screen.underline_scanline = byte >> 4 & 15;
2211
2328
  this.vg75_c001_00_cmd += 1;
2212
2329
  return;
2213
2330
  }
@@ -2301,6 +2418,9 @@ class Memory {
2301
2418
  // src/lib/core/rk86_runner.ts
2302
2419
  class Runner {
2303
2420
  paused = false;
2421
+ turbo = false;
2422
+ hardware_id_enabled = false;
2423
+ stc_streak = 0;
2304
2424
  tracer = null;
2305
2425
  last_instructions = [];
2306
2426
  previous_batch_time = 0;
@@ -2322,7 +2442,7 @@ class Runner {
2322
2442
  this.machine.cpu.jump(63488);
2323
2443
  }
2324
2444
  interrupt(iff) {
2325
- if (!this.sound)
2445
+ if (!this.sound || this.turbo)
2326
2446
  return;
2327
2447
  if (this.last_iff == iff)
2328
2448
  return;
@@ -2347,8 +2467,11 @@ class Runner {
2347
2467
  }
2348
2468
  }
2349
2469
  execute(options = {}) {
2350
- const { terminate_address, on_terminate, exit_on_halt, on_batch_complete, turbo } = options;
2470
+ const { terminate_address, on_terminate, exit_on_halt, on_halt, on_batch_complete } = options;
2471
+ if (options.turbo !== undefined)
2472
+ this.turbo = options.turbo;
2351
2473
  clearTimeout(this.execute_timer);
2474
+ const turbo = this.turbo;
2352
2475
  const bursts = turbo ? 100 : 1;
2353
2476
  for (let burst = 0;burst < bursts; burst++) {
2354
2477
  if (this.paused)
@@ -2365,10 +2488,21 @@ class Runner {
2365
2488
  if (this.last_instructions.length > 5) {
2366
2489
  this.last_instructions.shift();
2367
2490
  }
2491
+ const opcode_pc = this.machine.cpu.pc;
2368
2492
  this.machine.memory.invalidate_access_variables();
2369
2493
  const instruction_ticks = this.machine.cpu.instruction();
2370
2494
  batch_ticks += instruction_ticks;
2371
2495
  this.total_ticks += instruction_ticks;
2496
+ if (this.hardware_id_enabled) {
2497
+ if (this.machine.memory.read_raw(opcode_pc) === 55) {
2498
+ if (++this.stc_streak >= 4) {
2499
+ this.stc_streak = 0;
2500
+ this.fire_hardware_id();
2501
+ }
2502
+ } else {
2503
+ this.stc_streak = 0;
2504
+ }
2505
+ }
2372
2506
  if (this.tracer) {
2373
2507
  this.tracer("after");
2374
2508
  if (this.paused)
@@ -2382,9 +2516,13 @@ class Runner {
2382
2516
  on_terminate?.();
2383
2517
  return;
2384
2518
  }
2385
- if (exit_on_halt && this.machine.memory.read_raw(this.machine.cpu.pc) === 118) {
2386
- on_terminate?.();
2387
- return;
2519
+ if (this.machine.memory.read_raw(this.machine.cpu.pc) === 118) {
2520
+ if (exit_on_halt) {
2521
+ on_terminate?.();
2522
+ return;
2523
+ }
2524
+ if (on_halt && on_halt())
2525
+ return;
2388
2526
  }
2389
2527
  }
2390
2528
  const now = performance.now();
@@ -2407,6 +2545,13 @@ class Runner {
2407
2545
  this.machine.cpu.jump(63488);
2408
2546
  this.machine.keyboard.reset();
2409
2547
  }
2548
+ fire_hardware_id() {
2549
+ const colorIdx = COLOR_MODES.indexOf(this.machine.screen.color_mode);
2550
+ this.machine.cpu.set_a(1);
2551
+ this.machine.cpu.set_b(colorIdx < 0 ? 0 : colorIdx);
2552
+ this.machine.cpu.set_c(this.turbo ? 1 : 0);
2553
+ this.machine.cpu.cf = 0;
2554
+ }
2410
2555
  }
2411
2556
 
2412
2557
  // src/lib/core/rk86_screen.ts
@@ -2427,6 +2572,9 @@ class Screen {
2427
2572
  video_memory_base = 0;
2428
2573
  video_memory_size = 0;
2429
2574
  transparent_attr = false;
2575
+ char_height = 10;
2576
+ underline_scanline = 7;
2577
+ color_mode = DEFAULT_COLOR_MODE;
2430
2578
  ready = false;
2431
2579
  renderer;
2432
2580
  constructor(machine) {
@@ -2486,6 +2634,11 @@ class Screen {
2486
2634
  }
2487
2635
  last_flip_ticks = 0;
2488
2636
  tick_cursor(total_ticks, ticks_per_flip) {
2637
+ if (this.machine.runner.turbo) {
2638
+ this.cursor_state = true;
2639
+ this.last_flip_ticks = total_ticks;
2640
+ return;
2641
+ }
2489
2642
  while (total_ticks - this.last_flip_ticks >= ticks_per_flip) {
2490
2643
  this.cursor_state = !this.cursor_state;
2491
2644
  this.last_flip_ticks += ticks_per_flip;
@@ -2502,7 +2655,7 @@ class Screen {
2502
2655
  this.width = width;
2503
2656
  this.height = height;
2504
2657
  this.video_memory_size = width * height;
2505
- this.machine.ui.update_screen_geometry(this.width, this.height);
2658
+ this.machine.ui.update_screen_geometry(this.width, this.height, this.char_height);
2506
2659
  if (this.last_width === this.width && this.last_height === this.height)
2507
2660
  return;
2508
2661
  this.machine.log(`\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D \u0440\u0430\u0437\u043C\u0435\u0440 \u044D\u043A\u0440\u0430\u043D\u0430: ${width} x ${height}`);
@@ -2511,6 +2664,12 @@ class Screen {
2511
2664
  if (this.last_video_memory_base !== -1)
2512
2665
  this.ready = true;
2513
2666
  }
2667
+ set_char_height(char_height) {
2668
+ if (this.char_height === char_height)
2669
+ return;
2670
+ this.char_height = char_height;
2671
+ this.machine.ui.update_screen_geometry(this.width, this.height, this.char_height);
2672
+ }
2514
2673
  last_video_memory_base = -1;
2515
2674
  set_video_memory(base) {
2516
2675
  this.video_memory_base = base;
@@ -2848,16 +3007,17 @@ class TerminalRenderer {
2848
3007
  const dim = "\x1B[2m";
2849
3008
  const reset = "\x1B[0m";
2850
3009
  const w = screen.width;
2851
- const ANSI_FG = ["37", "33", "35", "31", "36", "32", "34", "30"];
3010
+ const mode = screen.color_mode;
2852
3011
  let output = "\x1B[H";
2853
3012
  output += `${dim}\u250C${"\u2500".repeat(w)}\u2510${reset}
2854
3013
  `;
2855
3014
  const transparent = screen.transparent_attr;
3015
+ const offset = hasCellOffset(mode) && !transparent;
2856
3016
  const blinkOff = Math.floor(Date.now() / 320) % 2 === 1;
2857
3017
  const FA_PENDING = -1;
2858
3018
  let addr = screen.video_memory_base;
2859
3019
  let frameStopped = false;
2860
- let color = 0;
3020
+ let latchedAttrs = 0;
2861
3021
  let blink = false;
2862
3022
  for (let y = 0;y < screen.height; y++) {
2863
3023
  let line = `${dim}\u2502${reset}`;
@@ -2877,23 +3037,23 @@ class TerminalRenderer {
2877
3037
  continue;
2878
3038
  }
2879
3039
  if (raw >= 240) {
2880
- cells[cellCount++] = { ch: 0, color, blink };
3040
+ cells[cellCount++] = { ch: 0, attrs: latchedAttrs, blink, isFA: false };
2881
3041
  rowStopped = true;
2882
3042
  if (raw >= 248)
2883
3043
  frameStopped = true;
2884
3044
  } else if (raw >= 192) {
2885
- cells[cellCount++] = { ch: 0, color, blink };
3045
+ cells[cellCount++] = { ch: 0, attrs: latchedAttrs, blink, isFA: false };
2886
3046
  } else if (raw >= 128) {
2887
- color = (raw & 1) << 2 | (raw & 12) >> 2;
3047
+ latchedAttrs = raw;
2888
3048
  blink = (raw & 2) !== 0;
2889
- cells[cellCount++] = { ch: FA_PENDING, color, blink };
3049
+ cells[cellCount++] = { ch: FA_PENDING, attrs: latchedAttrs, blink, isFA: true };
2890
3050
  fifoFlag = true;
2891
3051
  } else {
2892
- cells[cellCount++] = { ch: raw, color, blink };
3052
+ cells[cellCount++] = { ch: raw, attrs: latchedAttrs, blink, isFA: false };
2893
3053
  }
2894
3054
  }
2895
3055
  while (cellCount < w)
2896
- cells[cellCount++] = { ch: 0, color, blink };
3056
+ cells[cellCount++] = { ch: 0, attrs: latchedAttrs, blink, isFA: false };
2897
3057
  let fifoIdx = 0;
2898
3058
  for (let x = 0;x < w; ++x) {
2899
3059
  if (cells[x].ch === FA_PENDING) {
@@ -2910,6 +3070,7 @@ class TerminalRenderer {
2910
3070
  for (let x = 0;x < w; x++) {
2911
3071
  const raw = memory.read(addr + x);
2912
3072
  let ch;
3073
+ let isFA = false;
2913
3074
  if (rowStopped) {
2914
3075
  ch = 0;
2915
3076
  } else if (raw >= 240) {
@@ -2920,27 +3081,46 @@ class TerminalRenderer {
2920
3081
  } else if (raw >= 192) {
2921
3082
  ch = 0;
2922
3083
  } else if (raw >= 128) {
2923
- color = (raw & 1) << 2 | (raw & 12) >> 2;
3084
+ latchedAttrs = raw;
2924
3085
  blink = (raw & 2) !== 0;
2925
3086
  ch = 0;
3087
+ isFA = true;
2926
3088
  } else {
2927
3089
  ch = raw;
2928
3090
  }
2929
- cells[x] = { ch, color, blink };
3091
+ cells[x] = { ch, attrs: latchedAttrs, blink, isFA };
2930
3092
  }
2931
3093
  addr += w;
2932
3094
  }
2933
- let prevColor = -1;
3095
+ let prevAnsi = -1;
3096
+ let prevReverse = false;
3097
+ let prevUnderline = false;
2934
3098
  for (let x = 0;x < w; x++) {
2935
3099
  const cell = cells[x];
2936
3100
  const ch = cell.blink && blinkOff ? 0 : cell.ch;
2937
3101
  const glyph = rk86char(ch);
2938
- if (cell.color !== prevColor) {
2939
- line += `\x1B[${ANSI_FG[cell.color]}m`;
2940
- prevColor = cell.color;
3102
+ let colorAttrs = cell.attrs;
3103
+ if (offset && x + 1 < w && cells[x + 1].isFA) {
3104
+ colorAttrs = cells[x + 1].attrs;
3105
+ }
3106
+ const ansi = rgbToAnsiBaseFg(attrToRgb(mode, colorAttrs));
3107
+ const reverse = !cell.isFA && (cell.attrs & 16) !== 0;
3108
+ const underline = !cell.isFA && (cell.attrs & 32) !== 0;
3109
+ if (ansi !== prevAnsi) {
3110
+ line += `\x1B[${ansi}m`;
3111
+ prevAnsi = ansi;
3112
+ }
3113
+ if (reverse !== prevReverse) {
3114
+ line += reverse ? `\x1B[7m` : `\x1B[27m`;
3115
+ prevReverse = reverse;
3116
+ }
3117
+ if (underline !== prevUnderline) {
3118
+ line += underline ? `\x1B[4m` : `\x1B[24m`;
3119
+ prevUnderline = underline;
2941
3120
  }
2942
3121
  if (x === screen.cursor_x && y === screen.cursor_y) {
2943
3122
  line += `\x1B[4m${glyph}\x1B[24m`;
3123
+ prevUnderline = false;
2944
3124
  } else {
2945
3125
  line += glyph;
2946
3126
  }
@@ -3106,6 +3286,8 @@ function printHelp() {
3106
3286
  --snapshot <\u0444\u0430\u0439\u043B> \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0441\u043D\u0438\u043C\u043E\u043A \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F (JSON) \u043F\u0440\u0438 \u0432\u044B\u0445\u043E\u0434\u0435
3107
3287
  --input <seq> \u0438\u043D\u044A\u0435\u043A\u0446\u0438\u044F \u043A\u043B\u0430\u0432\u0438\u0448 (\u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E): KeyA,Digit1,Enter,...
3108
3288
  \u0442\u043E\u043A\u0435\u043D *N \u0437\u0430\u0434\u0430\u0451\u0442 \u043F\u0430\u0443\u0437\u0443 N \u043C\u0441 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 *200)
3289
+ --color <0|1|2|3> \u0440\u0435\u0436\u0438\u043C \u0446\u0432\u0435\u0442\u0430: 0=\u0447/\u0431 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E), 1=\u0422\u043E\u043B\u043A\u0430\u043B\u0438\u043D,
3290
+ 2=\u0410\u043A\u0438\u043C\u0435\u043D\u043A\u043E, 3=\u0410\u043F\u043E\u0433\u0435\u0439
3109
3291
  --online \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043E\u043D\u043B\u0430\u0439\u043D-\u044D\u043C\u0443\u043B\u044F\u0442\u043E\u0440\u0435 rk86.ru
3110
3292
 
3111
3293
  \u041F\u0440\u0438\u043C\u0435\u0440\u044B:
@@ -3210,6 +3392,8 @@ async function main() {
3210
3392
  const screenFile = arg(args, "--screen");
3211
3393
  const snapshotFile = arg(args, "--snapshot");
3212
3394
  const goViaMonitor = arg(args, "-G", undefined, addrRe, parseAddr);
3395
+ const colorIdx = arg(args, "--color", "0", /^[0-3]$/, (v) => parseInt(v, 10)) ?? 0;
3396
+ const colorMode = COLOR_MODES[colorIdx];
3213
3397
  let inputSeq = arg(args, "--input");
3214
3398
  if (goViaMonitor !== undefined) {
3215
3399
  const hex2 = goViaMonitor.toString(16).toUpperCase();
@@ -3229,8 +3413,11 @@ async function main() {
3229
3413
  const machine = machineBuilder;
3230
3414
  machine.ui = new TerminalUI;
3231
3415
  machine.memory = new Memory(machine);
3416
+ io.input = (port) => machine.memory.read(port | port << 8);
3417
+ io.output = (port, w8) => machine.memory.write(port | port << 8, w8);
3232
3418
  machine.cpu = new I8080(machine);
3233
3419
  machine.screen = new Screen(machine);
3420
+ machine.screen.color_mode = colorMode;
3234
3421
  machine.tape = new Tape(machine);
3235
3422
  machine.runner = new Runner(machine);
3236
3423
  machine.memory.update_ruslat = machine.ui.update_ruslat;