badline 0.2.1 → 0.4.0

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/hooks/session-start.sh +57 -0
  3. data/.claude/settings.json +14 -0
  4. data/CHANGELOG.md +107 -0
  5. data/CLAUDE.md +51 -16
  6. data/CONTRIBUTING.md +95 -3
  7. data/README.md +119 -51
  8. data/SECURITY.md +9 -0
  9. data/doc/pinned-behaviour.md +293 -39
  10. data/exe/badline-ruby +59 -0
  11. data/lib/badline/address_bus.rb +28 -14
  12. data/lib/badline/addressable.rb +1 -1
  13. data/lib/badline/audio/aiff.rb +8 -3
  14. data/lib/badline/audio/bare_player.rb +20 -3
  15. data/lib/badline/audio/cli.rb +17 -12
  16. data/lib/badline/audio/console.rb +12 -58
  17. data/lib/badline/audio/machine_player.rb +9 -6
  18. data/lib/badline/audio/playback.rb +3 -0
  19. data/lib/badline/audio/renderer.rb +3 -3
  20. data/lib/badline/audio/sdl_sink.rb +20 -52
  21. data/lib/badline/audio/stream.rb +117 -0
  22. data/lib/badline/audio/terminal.rb +90 -0
  23. data/lib/badline/audio.rb +3 -2
  24. data/lib/badline/cartridge/easy_flash.rb +6 -2
  25. data/lib/badline/cartridge/epyx_fastload.rb +1 -1
  26. data/lib/badline/cartridge/geo_ram.rb +63 -0
  27. data/lib/badline/cartridge/mach5.rb +1 -1
  28. data/lib/badline/cartridge/magic_desk.rb +5 -1
  29. data/lib/badline/cartridge/ocean.rb +5 -2
  30. data/lib/badline/cartridge/rex_utility.rb +1 -1
  31. data/lib/badline/cartridge/rgcd.rb +1 -1
  32. data/lib/badline/cartridge/simons_basic.rb +2 -2
  33. data/lib/badline/cartridge/westermann.rb +2 -2
  34. data/lib/badline/cartridge.rb +9 -0
  35. data/lib/badline/checkpoint.rb +98 -0
  36. data/lib/badline/cia/interrupt_register.rb +34 -10
  37. data/lib/badline/cia.rb +19 -10
  38. data/lib/badline/color_memory.rb +2 -2
  39. data/lib/badline/computer.rb +34 -14
  40. data/lib/badline/cpu.rb +2 -2
  41. data/lib/badline/gui/application.rb +70 -26
  42. data/lib/badline/gui/gamepads.rb +22 -22
  43. data/lib/badline/gui/joy_map.rb +1 -1
  44. data/lib/badline/gui/key_map.rb +6 -1
  45. data/lib/badline/gui/pane.rb +7 -6
  46. data/lib/badline/gui/screen_pane.rb +9 -13
  47. data/lib/badline/gui/window.rb +35 -20
  48. data/lib/badline/gui.rb +1 -1
  49. data/lib/badline/input/mouse1351.rb +21 -6
  50. data/lib/badline/input/paddles.rb +11 -6
  51. data/lib/badline/kernal_trap/channel.rb +102 -7
  52. data/lib/badline/kernal_trap/drive/block_commands.rb +108 -0
  53. data/lib/badline/kernal_trap/drive/channels.rb +53 -0
  54. data/lib/badline/kernal_trap/drive/memory.rb +109 -0
  55. data/lib/badline/kernal_trap/drive/parameters.rb +28 -0
  56. data/lib/badline/kernal_trap/drive/status.rb +3 -0
  57. data/lib/badline/kernal_trap/drive/write_file.rb +41 -0
  58. data/lib/badline/kernal_trap/drive/writes.rb +92 -0
  59. data/lib/badline/kernal_trap/drive.rb +109 -131
  60. data/lib/badline/kernal_trap/file.rb +6 -14
  61. data/lib/badline/kernal_trap/load.rb +6 -0
  62. data/lib/badline/kernal_trap/save.rb +13 -7
  63. data/lib/badline/keyboard.rb +19 -3
  64. data/lib/badline/media.rb +44 -13
  65. data/lib/badline/memory.rb +2 -2
  66. data/lib/badline/options.rb +209 -0
  67. data/lib/badline/ram_expansion/banking.rb +30 -0
  68. data/lib/badline/ram_expansion/plus256k.rb +38 -0
  69. data/lib/badline/ram_expansion/plus60k.rb +31 -0
  70. data/lib/badline/ram_expansion/unexpanded.rb +22 -0
  71. data/lib/badline/ram_expansion.rb +21 -0
  72. data/lib/badline/region.rb +51 -0
  73. data/lib/badline/rom.rb +14 -5
  74. data/lib/badline/sdl/events.rb +47 -0
  75. data/lib/badline/sdl/functions.rb +73 -0
  76. data/lib/badline/sdl.rb +67 -0
  77. data/lib/badline/sid/catch_up.rb +110 -0
  78. data/lib/badline/sid/decimator.rb +6 -1
  79. data/lib/badline/sid/filter.rb +5 -5
  80. data/lib/badline/sid/waveform/fast_forward.rb +10 -10
  81. data/lib/badline/sid/waveform/noise_writeback.rb +32 -14
  82. data/lib/badline/sid/waveform.rb +8 -8
  83. data/lib/badline/sid.rb +19 -93
  84. data/lib/badline/storage/d64_image.rb +10 -0
  85. data/lib/badline/storage/d71_image.rb +12 -0
  86. data/lib/badline/storage/d81_image.rb +9 -0
  87. data/lib/badline/storage/disk_image/bam.rb +111 -0
  88. data/lib/badline/storage/disk_image/directory.rb +79 -0
  89. data/lib/badline/storage/disk_image/writing.rb +110 -0
  90. data/lib/badline/storage/disk_image.rb +49 -4
  91. data/lib/badline/storage/host_directory.rb +17 -4
  92. data/lib/badline/storage/sid_file.rb +36 -2
  93. data/lib/badline/storage.rb +18 -1
  94. data/lib/badline/time_of_day.rb +11 -2
  95. data/lib/badline/version.rb +1 -1
  96. data/lib/badline/via/control_lines.rb +104 -0
  97. data/lib/badline/via/interrupt_register.rb +58 -0
  98. data/lib/badline/via/shift_register.rb +135 -0
  99. data/lib/badline/via/timer1.rb +82 -0
  100. data/lib/badline/via/timer2.rb +84 -0
  101. data/lib/badline/via.rb +262 -0
  102. data/lib/badline/vic/bank.rb +4 -3
  103. data/lib/badline/vic/color_patches.rb +6 -3
  104. data/lib/badline/vic/display_state.rb +4 -0
  105. data/lib/badline/vic/graphics_shifter.rb +49 -8
  106. data/lib/badline/vic/registers.rb +19 -27
  107. data/lib/badline/vic/sequencer.rb +18 -18
  108. data/lib/badline/vic/sequencer_output.rb +4 -4
  109. data/lib/badline/vic/sprite/internal_bus.rb +4 -0
  110. data/lib/badline/vic/sprite/shifter.rb +25 -3
  111. data/lib/badline/vic/sprite.rb +5 -4
  112. data/lib/badline/vic/sprites.rb +8 -3
  113. data/lib/badline/vic.rb +98 -46
  114. data/lib/badline.rb +5 -0
  115. data/packaging/homebrew/badline.rb +34 -0
  116. metadata +37 -20
  117. data/exe/badline +0 -75
  118. data/exe/badline-sid +0 -34
  119. data/lib/badline/audio/options.rb +0 -137
data/README.md CHANGED
@@ -10,22 +10,36 @@ are modelled at the cycle level.
10
10
 
11
11
  It runs programs, disk and tape images, cartridges and SID tunes, and
12
12
  the SDL2 front end supports the keyboard, joysticks, game controllers,
13
- paddles and a 1351 mouse. There is no live sound yet, and emulation runs
14
- slower than a real C64. See [What's emulated](#whats-emulated) for the
13
+ paddles and a 1351 mouse. See [What's emulated](#whats-emulated) for the
15
14
  details.
16
15
 
17
- ## Requirements
16
+ It comes in two builds of the same emulator:
18
17
 
19
- Ruby 4.0 or newer, and SDL2:
18
+ - **`badline`**, compiled ahead of time with
19
+ [Spinel](https://github.com/matz/spinel). It runs in real time with
20
+ sound, so it's the one to play games with.
21
+ - **`badline-ruby`**, which comes with the Ruby gem and runs on CRuby. It
22
+ runs slower than a real C64, so its sound is off by default. The gem is
23
+ also a library, which runs a `Badline::Computer` from Ruby without a
24
+ window.
25
+
26
+ ## Installation
27
+
28
+ The native `badline`, with Homebrew:
20
29
 
21
30
  ```sh
22
- brew install sdl2 # macOS
23
- apt install libsdl2-dev # Debian/Ubuntu
31
+ brew install elektronaut/tap/badline
24
32
  ```
25
33
 
26
- ## Installation
34
+ The formula builds it from the release's generated C, so it needs no
35
+ Spinel or Ruby. To build it from a checkout instead, see
36
+ [native/README.md](native/README.md).
37
+
38
+ The gem needs Ruby 4.0 or newer and the SDL2 library:
27
39
 
28
40
  ```sh
41
+ brew install sdl2 # macOS
42
+ apt install libsdl2-2.0-0 # Debian/Ubuntu
29
43
  gem install badline
30
44
  ```
31
45
 
@@ -37,29 +51,51 @@ Run `badline` with no arguments to boot to the BASIC prompt, or give it
37
51
  something to load:
38
52
 
39
53
  ```sh
40
- badline # READY.
41
- badline game.prg # Load and run a program
42
- badline game.d64 # Mount a disk image as device 8 and load it
43
- badline game.tap # Insert a tape and load it
44
- badline game.crt # Attach a cartridge
45
- badline tune.sid # Play a SID tune
46
- badline ~/c64 # Mount a directory as device 8
54
+ badline # READY.
55
+ badline game.prg # Load and run a program
56
+ badline game.d64 # Mount a disk image as device 8 and load it
57
+ badline game.tap # Insert a tape and load it
58
+ badline game.crt # Attach a cartridge
59
+ badline tune.sid # Play a SID tune
60
+ badline ~/c64 # Mount a directory as device 8
47
61
  ```
48
62
 
63
+ `badline-ruby` takes the same media and the same options, with the
64
+ differences noted below.
65
+
49
66
  Programs, disk and tape images and SID tunes start automatically, and
50
67
  a cartridge starts itself. A mounted directory waits for you to `LOAD`
51
68
  from it. `--no-autostart` attaches the media and stops at `READY.`, so
52
- you can type the `LOAD` yourself. `--song N` picks a subtune of a
53
- `.sid` file, `--sid 8580` fits the newer SID, and `--disable-jit` runs
54
- without YJIT, which is otherwise switched on at startup.
55
- `badline --help` lists the options.
69
+ you can type the `LOAD` yourself. `--read-only` mounts a disk image
70
+ write-protected, so the drive reports `26,WRITE PROTECT ON` for any
71
+ write and the image file stays as it was. `--song N` picks a subtune of a
72
+ `.sid` file and `--sid 8580` fits the newer SID. `badline-ruby` also
73
+ takes `--disable-jit`, which runs without YJIT, otherwise switched on at
74
+ startup. `--help` lists the options.
75
+
76
+ The KERNAL, BASIC and character ROMs come with the gem. To run other
77
+ images, such as a patched KERNAL, point `BADLINE_ROM_PATH` at a
78
+ directory that holds `kernal.rom`, `basic.rom` and `character.rom`,
79
+ plus `eapi/eapi-am29f040-14` if you attach EasyFlash cartridges. From
80
+ Ruby, `Badline.rom_path = dir` does the same before a
81
+ `Badline::Computer` is built, and `nil` restores the bundled set.
82
+
83
+ `badline` plays the SID through the host's audio device, and `F10`
84
+ mutes and unmutes it. `--no-sound` turns it off. The window is paced by
85
+ the display's vsync; `--no-vsync` paces it by a timer, or by the sound
86
+ while it plays.
87
+
88
+ In `badline-ruby` sound is off by default, and `--sound` turns it on.
89
+ The machine runs below real time there, so the sound stutters: it plays
90
+ in bursts with silent gaps between them, at the right pitch, and never
91
+ slows the emulation down.
56
92
 
57
93
  ## Media
58
94
 
59
95
  | Format | Handling |
60
96
  |--------|----------|
61
97
  | `.prg`, `.p00` | Loaded into memory after boot. A program at the BASIC start (`$0801`) is `RUN`, anything else is left for you to `SYS` |
62
- | `.d64`, `.d71`, `.d81` | Mounted read-only as device 8, then `LOAD"*",8,1` and `RUN` |
98
+ | `.d64`, `.d71`, `.d81` | Mounted read-write as device 8, then `LOAD"*",8,1` and `RUN`. Writes go straight back to the image file. With `--read-only`, or an image the host can't write, it acts as a write-protected disk |
63
99
  | `.t64` | Mounted read-only as device 8 and loaded like a disk image. The files load by name, and no tape is involved |
64
100
  | `.tap` | Inserted in the datasette with PLAY pressed, then `LOAD` and `RUN`. It loads at the speed of a real tape |
65
101
  | `.crt` | The hardware types listed under [Cartridges](#whats-emulated). Other types are rejected |
@@ -70,36 +106,47 @@ There is no 1541. Device 8 works by trapping the KERNAL's `LOAD` and
70
106
  `SAVE` routines and its serial bus primitives, so files open by name
71
107
  through `OPEN` and `CHRIN` as well. The command channel answers `I`,
72
108
  `B-P` and `U1` block reads, which covers loaders that read blocks
73
- directly. Loaders that upload their own code to the drive with `M-W` and
74
- `M-E`, and copy protection that reads raw GCR, won't work.
109
+ directly. On a disk image it also takes `SAVE` (with `@0:` to replace a
110
+ file), files opened for writing or appending, `S` to scratch, `U2` and
111
+ `B-W` block writes, and `B-A` and `B-F`. Loaders that upload their own
112
+ code to the drive with `M-W` and `M-E`, and copy protection that reads
113
+ raw GCR, won't work.
114
+
115
+ `Badline::Media.insert_disk(computer, path)` swaps the disk image or
116
+ directory in device 8 while the machine runs, for software that asks for
117
+ another disk. It takes `read_only: true`, and `Badline::Media.attach`
118
+ takes `disk: { read_only: true }`, to mount a disk image write-protected,
119
+ as the test harnesses under `bin/` do.
75
120
 
76
121
  ## Playing and rendering SID tunes
77
122
 
78
- `badline-sid` plays a `.sid` tune on the host's audio device, or with
79
- `--output` (or `-o`) renders it to a 16-bit PCM file instead. The
80
- output extension picks the format, `.wav` or `.aiff`.
123
+ `badline-ruby --headless` plays a `.sid` tune on the host's audio
124
+ device without opening the window, and `--audio-out` renders it to a
125
+ 16-bit PCM file instead. The file's extension picks the format, `.wav`
126
+ or `.aiff`. The native `badline` has both modes too.
81
127
 
82
128
  ```sh
83
- badline-sid tune.sid # play, length from HVSC
84
- badline-sid -s 3 tune.sid # play the third subtune
85
- badline-sid --seconds 180 tune.sid -o out.aiff
86
- badline-sid -s 3 --rate 48000 tune.sid -o out.wav
87
- badline-sid --sid 8580 tune.sid
88
- badline-sid --filter-chunk 1 tune.sid -o out.wav # exact filter, slower
129
+ badline-ruby --headless tune.sid # play, length from HVSC
130
+ badline-ruby --headless -s 3 tune.sid # play the third subtune
131
+ badline-ruby --seconds 180 tune.sid --audio-out out.aiff
132
+ badline-ruby -s 3 --rate 48000 tune.sid --audio-out out.wav
133
+ badline-ruby --headless --sid 8580 tune.sid
134
+ badline-ruby --filter-chunk 1 tune.sid --audio-out out.wav # exact filter, slower
89
135
  ```
90
136
 
91
- Both modes take the same options. `--song` (or `-s`) picks the subtune,
92
- counting from 1 as HVSC does, and defaults to the tune's own start
93
- song. Playback asks the device for 44.1 kHz and takes whatever rate it
137
+ Both modes take the same options. The window's own, `--no-autostart`
138
+ and `--sound`, don't apply to them. `--song` (or `-s`) picks the
139
+ subtune, counting from 1 as HVSC does, and defaults to the tune's own
140
+ start song. Playback asks the device for 44.1 kHz and takes whatever rate it
94
141
  offers, unless `--rate` says otherwise. Ctrl-C stops it.
95
142
 
96
- Played on a terminal, `badline-sid` shows the tune's name, author and
143
+ Played on a terminal, `--headless` shows the tune's name, author and
97
144
  release, the song number and the time played against the song's
98
145
  length. `n` or → skips to the next song, `p` or ← goes back one, space
99
146
  pauses and `q` quits. `--no-tui`, or output that isn't a terminal,
100
147
  gives plain progress output instead.
101
148
 
102
- A `.sid` file doesn't store its length, so `badline-sid` looks the
149
+ A `.sid` file doesn't store its length, so `badline-ruby` looks the
103
150
  tune up by MD5 in HVSC's `Songlengths.md5`. It finds the database
104
151
  through `--songlengths`, in a `DOCUMENTS` directory in any of the
105
152
  tune's parent directories (the layout of an HVSC collection), or under
@@ -109,10 +156,15 @@ tune's parent directories (the layout of an HVSC collection), or under
109
156
  PSID tunes run on a CPU and RAM with only the SID clocked, at about
110
157
  twice real time, so they play smoothly. RSID tunes set up their own
111
158
  interrupts, so they boot a full C64 first and run at about half real
112
- time. They render fine but stutter when played, and `badline-sid` says
159
+ time. They render fine but stutter when played, and `badline-ruby` says
113
160
  so when it falls behind. The filter steps four cycles at a time;
114
161
  `--filter-chunk 1` steps it every cycle, which is exact and takes about
115
- twice as long. `badline-sid --help` lists the options.
162
+ twice as long. `badline-ruby --help` lists the options.
163
+
164
+ The native `badline` takes the same options and runs the same code, so
165
+ it renders the same file sample for sample, and it plays RSID tunes
166
+ without stuttering. See
167
+ [native/README.md](native/README.md#without-the-window).
116
168
 
117
169
  ## Input
118
170
 
@@ -125,15 +177,15 @@ same-named host key:
125
177
  | `RUN/STOP` | `Escape` |
126
178
  | `CLR/HOME` | `Home` |
127
179
  | `INST/DEL` | `Backspace` |
128
- | `CRSR ⇔` / `CRSR ⇕` | `Right` / `Down` (add Shift for left and up) |
129
- | `←` / `↑` | `Left` / `Up` |
180
+ | `CRSR ⇔` / `CRSR ⇕` | `Right` / `Down`, and `Left` / `Up` for the shifted directions |
181
+ | `←` / `↑` | `` ` `` / `]` |
130
182
  | `CTRL` | `Left Ctrl` |
131
183
  | `C=` | `Left Alt` |
132
184
  | `@` | `\` |
133
185
  | `:` | `'` |
134
186
  | `£` | `End` |
135
187
  | `+` / `*` | Keypad `+` / Keypad `*` |
136
- | `RESTORE` | Not mapped |
188
+ | `RESTORE` | `Page Up` |
137
189
 
138
190
  `Tab` steps through the input modes and `Shift-Tab` steps back. The
139
191
  window title shows the current mode:
@@ -160,6 +212,9 @@ Control port 1's fire line is also the VIC-II's light pen input, so
160
212
  joystick 1's fire button and the 1351's left button in port 1 latch the
161
213
  light pen registers.
162
214
 
215
+ `F10` mutes and unmutes the sound, and the window title shows `[MUTED]`
216
+ while it's off.
217
+
163
218
  ## What's emulated
164
219
 
165
220
  - **6510**: every opcode, documented and undocumented, with per-cycle
@@ -167,14 +222,21 @@ light pen registers.
167
222
  [65x02 single step tests](https://github.com/SingleStepTests/65x02).
168
223
  `JAM` opcodes halt the CPU until reset.
169
224
  - **Memory**: banking through the 6510 port, including the cartridge
170
- `EXROM`/`GAME` lines and Ultimax mode.
225
+ `EXROM`/`GAME` lines and Ultimax mode. The +60K and +256K RAM
226
+ expansions fit with `Badline::Computer.new(ram_expansion: :plus60k)` or
227
+ `:plus256k`, banked through their register at `$D100`.
171
228
  - **VIC-II** (PAL 6569): the five standard graphics modes and the
172
229
  invalid ones, sprites with multicolour, expansion, priority and
173
230
  pixel-level collisions, raster interrupts, bad lines, sprite DMA, the
174
231
  border, VIC banks and the light pen.
232
+ `Badline::Computer.new(vic_model: :mos8565)` fits the C64C's 8565
233
+ instead, with its grey dots on colour register writes and its own
234
+ timing for mode splits, sprite multicolour splits and the light pen.
175
235
  - **CIA 1 and 2**: timers, time-of-day clocks with alarms, the serial
176
236
  shift register, interrupts, the keyboard matrix with its ghost keys,
177
- the control ports and the paddle multiplexer.
237
+ the control ports and the paddle multiplexer. The machine has the
238
+ original 6526s; `Badline::Computer.new(cia_model: :mos6526a)` fits the
239
+ C64C's 6526As instead, whose interrupt register timing differs.
178
240
  - **SID**: the 6581 and the 8580, with oscillators, ring modulation and
179
241
  sync, the envelope generator including the ADSR delay bug, the filter,
180
242
  and the RC network on the board that removes the DC offset from the
@@ -199,24 +261,30 @@ light pen registers.
199
261
  `Media.attach(computer, path, cartridge: { flash_jumper: true })`, with
200
262
  `bank_jumper: true` to run from the second 64K of a 128K image. The
201
263
  GMod2 EEPROM and the Retro Replay clock port aren't there.
264
+ - **GEO-RAM**: 64K to 4M of RAM seen through the `$DE00` page, with the
265
+ `$DFFE`/`$DFFF` page and block registers. It takes the expansion port,
266
+ so it can't sit alongside a cartridge:
267
+ `computer.attach_cartridge(Badline::Cartridge::GeoRAM.new(size: 512))`.
268
+ Its contents are lost when the emulator quits.
202
269
 
203
270
  Known gaps:
204
271
 
205
- - No live audio in the emulator window. The SID is emulated, but the
206
- whole machine runs below real time. `badline-sid` plays `.sid` tunes
207
- on their own.
272
+ - `badline-ruby` runs below real time, so its live audio stutters.
273
+ `badline` plays smoothly, and so do PSID tunes under
274
+ `badline-ruby --headless`.
208
275
  - No drive emulation, so fast loaders and anything else that runs code
209
- on the drive won't work (see [Media](#media)). Disk images are
210
- read-only.
211
- - No NTSC machine, no REU, and no `RESTORE` key.
276
+ on the drive won't work (see [Media](#media)). The command channel
277
+ doesn't rename, copy, format or validate disks, and `LOAD"$",8`
278
+ doesn't list a disk's directory yet.
279
+ - No NTSC machine and no REU.
212
280
  - The emulator window has no freeze button yet, so a freezer cartridge
213
281
  runs its menu but can't freeze a program.
214
282
 
215
283
  ## Contributing
216
284
 
217
- Bug reports and pull requests are welcome on
218
- [GitHub](https://github.com/elektronaut/badline).
219
- [CONTRIBUTING.md](CONTRIBUTING.md) covers running the tests and the
285
+ Bug reports, feature requests, and pull requests are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) first. Report security vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
286
+
287
+ [CONTRIBUTING.md](CONTRIBUTING.md) also covers running the tests and the
220
288
  commit format, and the project has a
221
289
  [code of conduct](CODE_OF_CONDUCT.md).
222
290
 
data/SECURITY.md ADDED
@@ -0,0 +1,9 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Don't report security vulnerabilities in public issues, pull requests, or discussions.
6
+
7
+ Report them privately through GitHub: open the repository's **Security** tab and choose **Report a vulnerability**, or go directly to https://github.com/elektronaut/badline/security/advisories/new.
8
+
9
+ The principles in [CONTRIBUTING.md](CONTRIBUTING.md) apply here too: describe what you observed, give minimal steps you actually ran, and include raw output. Report what you verified; the maintainers will assess the impact.