rk86 2.0.29 → 2.0.30

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 +1 -1
  3. package/rk86.js +90 -19
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.30",
4
4
  "description": "Эмулятор Радио-86РК (Intel 8080) для терминала",
5
5
  "bin": {
6
6
  "rk86": "rk86.js"
package/rk86.js CHANGED
@@ -871,7 +871,7 @@ import { basename } from "path";
871
871
  // packages/rk86/package.json
872
872
  var package_default = {
873
873
  name: "rk86",
874
- version: "2.0.28",
874
+ version: "2.0.29",
875
875
  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
876
  bin: {
877
877
  rk86: "rk86.js"
@@ -893,6 +893,59 @@ var package_default = {
893
893
  }
894
894
  };
895
895
 
896
+ // src/lib/core/rk86_colors.ts
897
+ var COLOR_MODES = ["mono", "color1", "color2", "color3"];
898
+ var DEFAULT_COLOR_MODE = "color1";
899
+ function attrToRgb(mode, attrs) {
900
+ const hglt = (attrs & 1) !== 0;
901
+ const gpa0 = (attrs & 4) !== 0;
902
+ const gpa1 = (attrs & 8) !== 0;
903
+ switch (mode) {
904
+ case "color1": {
905
+ const rgb = (gpa1 ? 255 : 0) | (gpa0 ? 65280 : 0) | (hglt ? 16711680 : 0);
906
+ return rgb === 0 ? 12632256 : rgb;
907
+ }
908
+ case "color2":
909
+ return (gpa0 ? 0 : 16711680) | (gpa1 ? 0 : 65280) | (hglt ? 0 : 255);
910
+ case "color3":
911
+ return (gpa0 ? 0 : 255) | (gpa1 ? 0 : 65280) | (hglt ? 0 : 16711680);
912
+ case "mono":
913
+ default:
914
+ return 12632256;
915
+ }
916
+ }
917
+ function hasCellOffset(mode) {
918
+ return mode !== "color3";
919
+ }
920
+ function rgbToAnsiBaseFg(rgb) {
921
+ const r = rgb >> 16 & 255;
922
+ const g = rgb >> 8 & 255;
923
+ const b = rgb & 255;
924
+ const palette = [
925
+ [30, 0, 0, 0],
926
+ [31, 255, 0, 0],
927
+ [32, 0, 255, 0],
928
+ [33, 255, 255, 0],
929
+ [34, 0, 0, 255],
930
+ [35, 255, 0, 255],
931
+ [36, 0, 255, 255],
932
+ [37, 255, 255, 255]
933
+ ];
934
+ let best = 37;
935
+ let bestDist = Infinity;
936
+ for (const [code, pr, pg, pb] of palette) {
937
+ const dr = r - pr;
938
+ const dg = g - pg;
939
+ const db = b - pb;
940
+ const dist = dr * dr + dg * dg + db * db;
941
+ if (dist < bestDist) {
942
+ bestDist = dist;
943
+ best = code;
944
+ }
945
+ }
946
+ return best;
947
+ }
948
+
896
949
  // src/lib/core/hex.ts
897
950
  function hex(v, prefix) {
898
951
  return v.toString(16).toUpperCase();
@@ -2347,7 +2400,7 @@ class Runner {
2347
2400
  }
2348
2401
  }
2349
2402
  execute(options = {}) {
2350
- const { terminate_address, on_terminate, exit_on_halt, on_batch_complete, turbo } = options;
2403
+ const { terminate_address, on_terminate, exit_on_halt, on_halt, on_batch_complete, turbo } = options;
2351
2404
  clearTimeout(this.execute_timer);
2352
2405
  const bursts = turbo ? 100 : 1;
2353
2406
  for (let burst = 0;burst < bursts; burst++) {
@@ -2382,9 +2435,13 @@ class Runner {
2382
2435
  on_terminate?.();
2383
2436
  return;
2384
2437
  }
2385
- if (exit_on_halt && this.machine.memory.read_raw(this.machine.cpu.pc) === 118) {
2386
- on_terminate?.();
2387
- return;
2438
+ if (this.machine.memory.read_raw(this.machine.cpu.pc) === 118) {
2439
+ if (exit_on_halt) {
2440
+ on_terminate?.();
2441
+ return;
2442
+ }
2443
+ if (on_halt && on_halt())
2444
+ return;
2388
2445
  }
2389
2446
  }
2390
2447
  const now = performance.now();
@@ -2427,6 +2484,7 @@ class Screen {
2427
2484
  video_memory_base = 0;
2428
2485
  video_memory_size = 0;
2429
2486
  transparent_attr = false;
2487
+ color_mode = DEFAULT_COLOR_MODE;
2430
2488
  ready = false;
2431
2489
  renderer;
2432
2490
  constructor(machine) {
@@ -2848,16 +2906,17 @@ class TerminalRenderer {
2848
2906
  const dim = "\x1B[2m";
2849
2907
  const reset = "\x1B[0m";
2850
2908
  const w = screen.width;
2851
- const ANSI_FG = ["37", "33", "35", "31", "36", "32", "34", "30"];
2909
+ const mode = screen.color_mode;
2852
2910
  let output = "\x1B[H";
2853
2911
  output += `${dim}\u250C${"\u2500".repeat(w)}\u2510${reset}
2854
2912
  `;
2855
2913
  const transparent = screen.transparent_attr;
2914
+ const offset = hasCellOffset(mode) && !transparent;
2856
2915
  const blinkOff = Math.floor(Date.now() / 320) % 2 === 1;
2857
2916
  const FA_PENDING = -1;
2858
2917
  let addr = screen.video_memory_base;
2859
2918
  let frameStopped = false;
2860
- let color = 0;
2919
+ let latchedAttrs = 0;
2861
2920
  let blink = false;
2862
2921
  for (let y = 0;y < screen.height; y++) {
2863
2922
  let line = `${dim}\u2502${reset}`;
@@ -2877,23 +2936,23 @@ class TerminalRenderer {
2877
2936
  continue;
2878
2937
  }
2879
2938
  if (raw >= 240) {
2880
- cells[cellCount++] = { ch: 0, color, blink };
2939
+ cells[cellCount++] = { ch: 0, attrs: latchedAttrs, blink, isFA: false };
2881
2940
  rowStopped = true;
2882
2941
  if (raw >= 248)
2883
2942
  frameStopped = true;
2884
2943
  } else if (raw >= 192) {
2885
- cells[cellCount++] = { ch: 0, color, blink };
2944
+ cells[cellCount++] = { ch: 0, attrs: latchedAttrs, blink, isFA: false };
2886
2945
  } else if (raw >= 128) {
2887
- color = (raw & 1) << 2 | (raw & 12) >> 2;
2946
+ latchedAttrs = raw;
2888
2947
  blink = (raw & 2) !== 0;
2889
- cells[cellCount++] = { ch: FA_PENDING, color, blink };
2948
+ cells[cellCount++] = { ch: FA_PENDING, attrs: latchedAttrs, blink, isFA: true };
2890
2949
  fifoFlag = true;
2891
2950
  } else {
2892
- cells[cellCount++] = { ch: raw, color, blink };
2951
+ cells[cellCount++] = { ch: raw, attrs: latchedAttrs, blink, isFA: false };
2893
2952
  }
2894
2953
  }
2895
2954
  while (cellCount < w)
2896
- cells[cellCount++] = { ch: 0, color, blink };
2955
+ cells[cellCount++] = { ch: 0, attrs: latchedAttrs, blink, isFA: false };
2897
2956
  let fifoIdx = 0;
2898
2957
  for (let x = 0;x < w; ++x) {
2899
2958
  if (cells[x].ch === FA_PENDING) {
@@ -2910,6 +2969,7 @@ class TerminalRenderer {
2910
2969
  for (let x = 0;x < w; x++) {
2911
2970
  const raw = memory.read(addr + x);
2912
2971
  let ch;
2972
+ let isFA = false;
2913
2973
  if (rowStopped) {
2914
2974
  ch = 0;
2915
2975
  } else if (raw >= 240) {
@@ -2920,24 +2980,30 @@ class TerminalRenderer {
2920
2980
  } else if (raw >= 192) {
2921
2981
  ch = 0;
2922
2982
  } else if (raw >= 128) {
2923
- color = (raw & 1) << 2 | (raw & 12) >> 2;
2983
+ latchedAttrs = raw;
2924
2984
  blink = (raw & 2) !== 0;
2925
2985
  ch = 0;
2986
+ isFA = true;
2926
2987
  } else {
2927
2988
  ch = raw;
2928
2989
  }
2929
- cells[x] = { ch, color, blink };
2990
+ cells[x] = { ch, attrs: latchedAttrs, blink, isFA };
2930
2991
  }
2931
2992
  addr += w;
2932
2993
  }
2933
- let prevColor = -1;
2994
+ let prevAnsi = -1;
2934
2995
  for (let x = 0;x < w; x++) {
2935
2996
  const cell = cells[x];
2936
2997
  const ch = cell.blink && blinkOff ? 0 : cell.ch;
2937
2998
  const glyph = rk86char(ch);
2938
- if (cell.color !== prevColor) {
2939
- line += `\x1B[${ANSI_FG[cell.color]}m`;
2940
- prevColor = cell.color;
2999
+ let attrs = cell.attrs;
3000
+ if (offset && x + 1 < w && cells[x + 1].isFA) {
3001
+ attrs = cells[x + 1].attrs;
3002
+ }
3003
+ const ansi = rgbToAnsiBaseFg(attrToRgb(mode, attrs));
3004
+ if (ansi !== prevAnsi) {
3005
+ line += `\x1B[${ansi}m`;
3006
+ prevAnsi = ansi;
2941
3007
  }
2942
3008
  if (x === screen.cursor_x && y === screen.cursor_y) {
2943
3009
  line += `\x1B[4m${glyph}\x1B[24m`;
@@ -3106,6 +3172,8 @@ function printHelp() {
3106
3172
  --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
3173
  --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
3174
  \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)
3175
+ --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,
3176
+ 2=\u0410\u043A\u0438\u043C\u0435\u043D\u043A\u043E, 3=\u0410\u043F\u043E\u0433\u0435\u0439
3109
3177
  --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
3178
 
3111
3179
  \u041F\u0440\u0438\u043C\u0435\u0440\u044B:
@@ -3210,6 +3278,8 @@ async function main() {
3210
3278
  const screenFile = arg(args, "--screen");
3211
3279
  const snapshotFile = arg(args, "--snapshot");
3212
3280
  const goViaMonitor = arg(args, "-G", undefined, addrRe, parseAddr);
3281
+ const colorIdx = arg(args, "--color", "0", /^[0-3]$/, (v) => parseInt(v, 10)) ?? 0;
3282
+ const colorMode = COLOR_MODES[colorIdx];
3213
3283
  let inputSeq = arg(args, "--input");
3214
3284
  if (goViaMonitor !== undefined) {
3215
3285
  const hex2 = goViaMonitor.toString(16).toUpperCase();
@@ -3231,6 +3301,7 @@ async function main() {
3231
3301
  machine.memory = new Memory(machine);
3232
3302
  machine.cpu = new I8080(machine);
3233
3303
  machine.screen = new Screen(machine);
3304
+ machine.screen.color_mode = colorMode;
3234
3305
  machine.tape = new Tape(machine);
3235
3306
  machine.runner = new Runner(machine);
3236
3307
  machine.memory.update_ruslat = machine.ui.update_ruslat;