pwn 0.5.762 → 0.5.763
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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/documentation/Banner.md +18 -0
- data/lib/pwn/plugins/repl/ai_console.rb +101 -1
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/plugins/repl/ai_console_spec.rb +149 -2
- data/third_party/pwn_rdoc.jsonl +3 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fec3b367c44874a819aa4892a5b3c507cf858ed4649eb31d295b0509e8747c83
|
|
4
|
+
data.tar.gz: d8a004b8eee5f7bd996ae8c9208f895d93354b09f9149050cb3e93d8632fda8c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7490168d3e50c940ff771fe6788f7373e89b15f96541a8d0cec29bd13b1cccdce7c377799a415628fcd899a0e912928bf3aeb5fef68c0cf0fda2217871f60d95
|
|
7
|
+
data.tar.gz: eb50e5646ee60e9394f92139b61c38901303fc6167b06f95cf559a413d88a09dbfa21eadeb1e46e8e9418295d348e53f81a62d4d4f25956ebb0284f7e9c2e572
|
data/Gemfile
CHANGED
data/documentation/Banner.md
CHANGED
|
@@ -14,4 +14,22 @@ welcome-banner # REPL command: redraw a random one
|
|
|
14
14
|
|
|
15
15
|
Add your own: drop a module in `lib/pwn/banner/` implementing `self.get`.
|
|
16
16
|
|
|
17
|
+
## pwn-ai startup pane
|
|
18
|
+
|
|
19
|
+
The curses console starts with a white miniature derived directly from
|
|
20
|
+
`PWN::Banner::WhiteRabbit.get`, without its dedication or wordmark. The full
|
|
21
|
+
banner remains unchanged. The rabbit is centered and aspect-contained in the
|
|
22
|
+
current pane: small panes use area-filtered 3×6 character strokes packed into
|
|
23
|
+
ordinary 2×4 Braille text dots; when the original ASCII fits, it is displayed
|
|
24
|
+
verbatim, centered without enlargement. This assumes typical 1:2 terminal cells.
|
|
25
|
+
Small panes lose punctuation detail, and Braille dot spacing depends on the font;
|
|
26
|
+
physically tiny panes cannot retain a recognizable rabbit. No image protocol,
|
|
27
|
+
image file, graphics probe or runtime image library is used.
|
|
28
|
+
|
|
29
|
+
No games or replays are computed until the animation pane first receives focus
|
|
30
|
+
via Ctrl+T/Ctrl+X. That enables games for the console lifetime. Ctrl+G cycles
|
|
31
|
+
games while focused; leaving the pane keeps the existing automatic animation,
|
|
32
|
+
not the startup rabbit. Resizing, modal input priority and the mission draft
|
|
33
|
+
retain their existing behavior. Monochrome terminals keep the same geometry.
|
|
34
|
+
|
|
17
35
|
[← Home](Home.md)
|
|
@@ -720,6 +720,7 @@ module PWN
|
|
|
720
720
|
@menu = nil
|
|
721
721
|
panes = @header_pane_height ? %i[mission session animation] : %i[mission session]
|
|
722
722
|
@focus = panes[(panes.index(@focus).to_i + 1) % panes.length]
|
|
723
|
+
@banner_activated = true if @focus == :animation
|
|
723
724
|
@banner_game&.controls
|
|
724
725
|
@banner_game_tick = nil
|
|
725
726
|
return
|
|
@@ -1227,6 +1228,9 @@ module PWN
|
|
|
1227
1228
|
# Artwork has no worker, IO, or request-progress meaning. The existing
|
|
1228
1229
|
# event-loop repaint supplies its clock, including while requests run.
|
|
1229
1230
|
def banner_frame(size, cells: false)
|
|
1231
|
+
# Startup is artwork only. Once focused, retain the existing seeded
|
|
1232
|
+
# automatic behavior on leaving the game; never return to the splash.
|
|
1233
|
+
@banner_activated ||= @focus == :animation
|
|
1230
1234
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
1231
1235
|
session = @pry.config.pwn_ai_session_id.to_s
|
|
1232
1236
|
if @banner_session != session
|
|
@@ -1236,6 +1240,11 @@ module PWN
|
|
|
1236
1240
|
@banner_seed = session.bytes.reduce(PWN::Banner::MINI_SEED) { |seed, byte| ((seed * 33) ^ byte) & 0xffffffff }
|
|
1237
1241
|
@banner_started = now
|
|
1238
1242
|
end
|
|
1243
|
+
unless @banner_activated
|
|
1244
|
+
art = rabbit_cells(size)
|
|
1245
|
+
return cells ? art : art.map { |row| row.map { |cell| cell[:glyph] }.join }
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1239
1248
|
cadence = cells ? @banner_frame_seconds : PWN::Banner::MINI_FRAME_SECONDS
|
|
1240
1249
|
frame = ((now - @banner_started) / cadence).floor % PWN::Banner::MINI_FRAME_COUNT
|
|
1241
1250
|
return playable_banner(size, now) if cells && @focus == :animation
|
|
@@ -1249,6 +1258,97 @@ module PWN
|
|
|
1249
1258
|
canvas
|
|
1250
1259
|
end
|
|
1251
1260
|
|
|
1261
|
+
# Rasterize the actual banner's punctuation, not a replacement rabbit.
|
|
1262
|
+
# These 3x6 character strokes retain the usual 1:2 terminal-cell aspect.
|
|
1263
|
+
# Only the dedication and wordmark are omitted; the full banner API is
|
|
1264
|
+
# unchanged. At native size use its original characters verbatim.
|
|
1265
|
+
RABBIT_STROKES = {
|
|
1266
|
+
' ' => [0, 0, 0, 0, 0, 0], '_' => [0, 0, 0, 0, 0, 7],
|
|
1267
|
+
'/' => [1, 1, 2, 2, 4, 4], '\\' => [4, 4, 2, 2, 1, 1],
|
|
1268
|
+
'{' => [1, 2, 2, 4, 2, 1], '}' => [4, 2, 2, 1, 2, 4],
|
|
1269
|
+
'Y' => [5, 5, 2, 2, 2, 2], '.' => [0, 0, 0, 0, 0, 2],
|
|
1270
|
+
'-' => [0, 0, 0, 7, 0, 0], '"' => [5, 5, 0, 0, 0, 0],
|
|
1271
|
+
'`' => [4, 2, 0, 0, 0, 0], ',' => [0, 0, 0, 0, 2, 4],
|
|
1272
|
+
';' => [0, 2, 0, 0, 2, 4], '|' => [2, 2, 2, 2, 2, 2]
|
|
1273
|
+
}.transform_values(&:freeze).freeze
|
|
1274
|
+
RABBIT_DOTS = [[0, 0, 1], [0, 1, 2], [0, 2, 4], [1, 0, 8], [1, 1, 16], [1, 2, 32], [0, 3, 64], [1, 3, 128]].map(&:freeze).freeze
|
|
1275
|
+
|
|
1276
|
+
def rabbit_source
|
|
1277
|
+
@rabbit_source ||= begin
|
|
1278
|
+
rows = PWN::Banner::WhiteRabbit.get.uncolorize.lines.map { |line| line.sub('R.I.P. Houdini', '').sub('pwn', '').rstrip }
|
|
1279
|
+
rows.shift while rows.first&.strip == ''
|
|
1280
|
+
rows.pop while rows.last&.strip == ''
|
|
1281
|
+
left = rows.reject { |row| row.strip.empty? }.map { |row| row.index(/\S/) }.min
|
|
1282
|
+
rows.map { |row| row[left..].to_s.freeze }.freeze
|
|
1283
|
+
end
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
def rabbit_cells(size)
|
|
1287
|
+
size = [size.to_i, 0].max
|
|
1288
|
+
return [] if size.zero?
|
|
1289
|
+
return @rabbit_cells if @rabbit_size == size
|
|
1290
|
+
|
|
1291
|
+
@rabbit_size = size
|
|
1292
|
+
rows = rabbit_source
|
|
1293
|
+
columns = rows.map(&:length).max
|
|
1294
|
+
if size >= [columns, rows.length].max
|
|
1295
|
+
left = (size - columns) / 2
|
|
1296
|
+
top = (size - rows.length) / 2
|
|
1297
|
+
return @rabbit_cells = Array.new(size) do |y|
|
|
1298
|
+
Array.new(size) do |x|
|
|
1299
|
+
glyph = y.between?(top, top + rows.length - 1) && x.between?(left, left + columns - 1) ? rows[y - top][x - left] : nil
|
|
1300
|
+
{ glyph: glyph || ' ', foreground: :white, background: :black }.freeze
|
|
1301
|
+
end.freeze
|
|
1302
|
+
end.freeze
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
source_width = columns * 3
|
|
1306
|
+
source = rows.flat_map do |row|
|
|
1307
|
+
6.times.map do |y|
|
|
1308
|
+
row.ljust(columns).chars.reduce(0) { |bits, char| (bits << 3) | RABBIT_STROKES.fetch(char)[y] }
|
|
1309
|
+
end
|
|
1310
|
+
end
|
|
1311
|
+
# Square Braille dots contain the same physical aspect as the ASCII
|
|
1312
|
+
# source. Fractional area sampling keeps thin punctuation on shrink.
|
|
1313
|
+
scale = [size * 2.0 / source_width, size * 4.0 / source.length].min
|
|
1314
|
+
width = [(source_width * scale).round, 1].max
|
|
1315
|
+
height = [(source.length * scale).round, 1].max
|
|
1316
|
+
left = ((size * 2) - width) / 2
|
|
1317
|
+
top = ((size * 4) - height) / 2
|
|
1318
|
+
threshold = size < 5 ? 0.02 : 0.12
|
|
1319
|
+
@rabbit_cells = Array.new(size) do |y|
|
|
1320
|
+
Array.new(size) do |x|
|
|
1321
|
+
mask = RABBIT_DOTS.sum do |dx, dy, bit|
|
|
1322
|
+
px = (x * 2) + dx - left
|
|
1323
|
+
py = (y * 4) + dy - top
|
|
1324
|
+
next 0 unless px.between?(0, width - 1) && py.between?(0, height - 1)
|
|
1325
|
+
|
|
1326
|
+
coverage = rabbit_coverage(source, source_width, [px.fdiv(width), py.fdiv(height), (px + 1).fdiv(width), (py + 1).fdiv(height)])
|
|
1327
|
+
coverage >= threshold ? bit : 0
|
|
1328
|
+
end
|
|
1329
|
+
{ glyph: mask.zero? ? ' ' : (0x2800 + mask).chr(Encoding::UTF_8), foreground: :white, background: :black }.freeze
|
|
1330
|
+
end.freeze
|
|
1331
|
+
end.freeze
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
# Fractional box-area coverage avoids nearest-neighbour holes.
|
|
1335
|
+
def rabbit_coverage(source, source_width, bounds)
|
|
1336
|
+
left, top, right, bottom = bounds
|
|
1337
|
+
left *= source_width
|
|
1338
|
+
right *= source_width
|
|
1339
|
+
top *= source.length
|
|
1340
|
+
bottom *= source.length
|
|
1341
|
+
ink = (top.floor...bottom.ceil).sum do |y|
|
|
1342
|
+
overlap_y = [y + 1, bottom].min - [y, top].max
|
|
1343
|
+
(left.floor...right.ceil).sum do |x|
|
|
1344
|
+
next 0 unless source[y][source_width - 1 - x] == 1
|
|
1345
|
+
|
|
1346
|
+
([x + 1, right].min - [x, left].max) * overlap_y
|
|
1347
|
+
end
|
|
1348
|
+
end
|
|
1349
|
+
ink.fdiv((right - left) * (bottom - top))
|
|
1350
|
+
end
|
|
1351
|
+
|
|
1252
1352
|
def automatic_banner(size, now)
|
|
1253
1353
|
key = [@banner_session, @banner_name, size, @banner_seed]
|
|
1254
1354
|
if @banner_demo_key != key
|
|
@@ -1296,7 +1396,7 @@ module PWN
|
|
|
1296
1396
|
row.first(size).each_with_index do |cell, x|
|
|
1297
1397
|
index = PALETTE.index(cell[:foreground].to_s)
|
|
1298
1398
|
background = PALETTE.index(cell[:background].to_s)
|
|
1299
|
-
colors = @banner_colors && (@banner_name != :falling_blocks || @banner_two_colors)
|
|
1399
|
+
colors = @banner_colors && (!@banner_activated || @banner_name != :falling_blocks || @banner_two_colors)
|
|
1300
1400
|
pair = if colors && index && background && (background == 7 || @banner_two_colors)
|
|
1301
1401
|
background == 7 ? 9 + index : 17 + (background * 8) + index
|
|
1302
1402
|
end
|
data/lib/pwn/version.rb
CHANGED
|
@@ -515,6 +515,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
515
515
|
pry = Pry.new
|
|
516
516
|
pry.config.pwn_ai_session_id = 'banner-one'
|
|
517
517
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: pry, input: StringIO.new, curses: nil, getch: nil)
|
|
518
|
+
console.instance_variable_set(:@banner_activated, true)
|
|
518
519
|
choices = PWN::Banner.mini_names.first(2)
|
|
519
520
|
allow(PWN::Banner).to receive(:mini_names).and_return(choices)
|
|
520
521
|
allow(choices).to receive(:sample).and_return(*choices)
|
|
@@ -536,6 +537,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
536
537
|
|
|
537
538
|
it 'centers capped retro artwork inside a larger cell-square canvas' do
|
|
538
539
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
540
|
+
console.instance_variable_set(:@banner_activated, true)
|
|
539
541
|
allow(PWN::Banner).to receive(:mini_names).and_return([:pacman])
|
|
540
542
|
allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0)
|
|
541
543
|
size = 24
|
|
@@ -551,6 +553,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
551
553
|
|
|
552
554
|
it 'paints falling blocks against the actual pane floor even beyond sixteen cells' do
|
|
553
555
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
556
|
+
console.instance_variable_set(:@banner_activated, true)
|
|
554
557
|
allow(PWN::Banner).to receive(:mini_names).and_return([:falling_blocks])
|
|
555
558
|
now = 100.0
|
|
556
559
|
allow(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC) { now }
|
|
@@ -594,7 +597,8 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
594
597
|
expect(painted.select { |y, x, _text, _color| y < 10 && x < 10 }.map { |_, _, text| text }.join).not_to match(/[PWN]/)
|
|
595
598
|
expect(painted.select { |y, x, text, _color| y.between?(1, 8) && x == 1 && text == ' ' * 8 }.length).to eq(8)
|
|
596
599
|
expect(painted.select { |y, _x, _text, _color| y < 10 }).to all(satisfy { |y, x, text, _color| y >= 0 && x >= 0 && x + console.width(text) <= 120 })
|
|
597
|
-
expect(PWN::Banner).
|
|
600
|
+
expect(PWN::Banner).not_to have_received(:mini_cells)
|
|
601
|
+
expect(console.banner_frame(8, cells: true).flatten.map { |cell| cell[:foreground] }.uniq).to eq([:white])
|
|
598
602
|
[[80, 36], [120, 24]].each do |columns, rows|
|
|
599
603
|
allow(curses).to receive_messages(cols: columns, lines: rows)
|
|
600
604
|
rectangles.clear
|
|
@@ -607,7 +611,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
607
611
|
console.draw
|
|
608
612
|
expect(console.instance_variable_get(:@header_text_column)).to eq(2)
|
|
609
613
|
expect(console.header_lines('ollama', 'fixture', 115).length).to be > 16
|
|
610
|
-
expect(PWN::Banner).
|
|
614
|
+
expect(PWN::Banner).not_to have_received(:mini_cells)
|
|
611
615
|
expect([console.instance_variable_get(:@editor).text, console.instance_variable_get(:@editor).cursor]).to eq(['retained draft', 4])
|
|
612
616
|
end
|
|
613
617
|
|
|
@@ -641,6 +645,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
641
645
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
642
646
|
console.instance_variable_set(:@width, 120)
|
|
643
647
|
console.instance_variable_set(:@height, 40)
|
|
648
|
+
console.instance_variable_set(:@banner_activated, true)
|
|
644
649
|
allow(console).to receive(:put)
|
|
645
650
|
allow(console).to receive(:box)
|
|
646
651
|
allow(PWN::Banner).to receive(:mini_cells).and_call_original
|
|
@@ -710,6 +715,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
710
715
|
pry = Pry.new
|
|
711
716
|
pry.config.pwn_ai_session_id = 'colored-session'
|
|
712
717
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: pry, input: StringIO.new, curses: nil, getch: nil)
|
|
718
|
+
console.instance_variable_set(:@banner_activated, true)
|
|
713
719
|
names = [:snake]
|
|
714
720
|
allow(PWN::Banner).to receive(:mini_names).and_return(names)
|
|
715
721
|
expect(names).to receive(:sample).twice.and_return(:snake)
|
|
@@ -734,6 +740,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
734
740
|
pry = Pry.new
|
|
735
741
|
pry.config.pwn_ai_session_id = 'fast-colored-session'
|
|
736
742
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: pry, input: StringIO.new, curses: nil, getch: nil)
|
|
743
|
+
console.instance_variable_set(:@banner_activated, true)
|
|
737
744
|
names = PWN::Banner.mini_names
|
|
738
745
|
allow(PWN::Banner).to receive(:mini_names).and_return(names)
|
|
739
746
|
expect(names).to receive(:sample).once.and_return(name)
|
|
@@ -1120,6 +1127,144 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
1120
1127
|
expect(console.instance_variable_get(:@focus)).to eq(:mission)
|
|
1121
1128
|
end
|
|
1122
1129
|
|
|
1130
|
+
it 'shows only the white rabbit before the first animation focus without computing games' do
|
|
1131
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1132
|
+
expect(PWN::Banner).not_to receive(:mini_cells)
|
|
1133
|
+
expect(PWN::Banner).not_to receive(:mini_frame)
|
|
1134
|
+
expect(PWN::Banner::MiniGame).not_to receive(:new)
|
|
1135
|
+
first = console.banner_frame(8, cells: true)
|
|
1136
|
+
expect(first.flatten.map { |cell| cell[:foreground] }.uniq).to eq([:white])
|
|
1137
|
+
expect(first.flatten.count { |cell| cell[:glyph] != ' ' }).to be > 16
|
|
1138
|
+
console.instance_variable_set(:@header_pane_height, 10)
|
|
1139
|
+
console.handle("\u0014")
|
|
1140
|
+
expect(console.banner_frame(8, cells: true)).to eq(first)
|
|
1141
|
+
expect(console.banner_frame(8).join).not_to match(/[PWN]/)
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
it 'uses fine two by four text dots without terminal image output' do
|
|
1145
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1146
|
+
[8, 12, 16, 18].each do |size|
|
|
1147
|
+
glyphs = console.banner_frame(size, cells: true).flatten.map { |cell| cell[:glyph] }
|
|
1148
|
+
expect(glyphs).to all(match(/\A[ \u2801-\u28ff]\z/))
|
|
1149
|
+
expect(glyphs.uniq.length).to be > 4
|
|
1150
|
+
end
|
|
1151
|
+
source = File.read(File.expand_path('../../../../../lib/pwn/plugins/repl/ai_console.rb', __dir__))
|
|
1152
|
+
expect(source).not_to include('graphics_response?', '\\e_G')
|
|
1153
|
+
end
|
|
1154
|
+
|
|
1155
|
+
it 'area-filters fractional source pixels instead of losing a bend to point sampling' do
|
|
1156
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1157
|
+
expect(console.rabbit_coverage([0b10, 0b01], 2, [0, 0, 1, 1])).to eq(0.5)
|
|
1158
|
+
expect(console.rabbit_coverage([0b10, 0b01], 2, [0.25, 0, 0.75, 0.5])).to eq(0.5)
|
|
1159
|
+
expect(console.rabbit_coverage([0b10, 0b01], 2, [0, 0, 0.5, 0.5])).to eq(1.0)
|
|
1160
|
+
expect(console.rabbit_coverage([0b10, 0b01], 2, [0.5, 0, 1, 0.5])).to eq(0.0)
|
|
1161
|
+
end
|
|
1162
|
+
|
|
1163
|
+
it 'derives every source row from WhiteRabbit.get and preserves the native art without labels' do
|
|
1164
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1165
|
+
source = PWN::Banner::WhiteRabbit.get.uncolorize
|
|
1166
|
+
expect(PWN::Banner::WhiteRabbit).to receive(:get).once.and_return(source)
|
|
1167
|
+
rows = source.lines.map { |row| row.sub('R.I.P. Houdini', '').sub('pwn', '').rstrip }.reject(&:empty?).map { |row| row[8..] }
|
|
1168
|
+
expect(console.rabbit_source).to eq(rows)
|
|
1169
|
+
expect(rows.join.chars.uniq - PWN::Plugins::REPL::AIConsole::Console::RABBIT_STROKES.keys).to be_empty
|
|
1170
|
+
[22, 34, 64].each do |size|
|
|
1171
|
+
art = console.banner_frame(size)
|
|
1172
|
+
top = (size - rows.length) / 2
|
|
1173
|
+
left = (size - rows.map(&:length).max) / 2
|
|
1174
|
+
rows.each_with_index { |row, y| expect(art[top + y][left, row.length]).to eq(row) }
|
|
1175
|
+
expect(art.join).not_to match(/pwn|Houdini|\e/)
|
|
1176
|
+
end
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1179
|
+
it 'contains the rabbit with centered aspect-correct padding across tiny resizes' do
|
|
1180
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1181
|
+
expect(PWN::Banner::MiniGame).not_to receive(:new)
|
|
1182
|
+
expect(console.banner_frame(0, cells: true)).to eq([])
|
|
1183
|
+
[1, 2, 3, 5, 8, 12, 13, 14, 16, 18, 8].each do |size|
|
|
1184
|
+
cells = console.banner_frame(size, cells: true)
|
|
1185
|
+
expect(cells.length).to eq(size)
|
|
1186
|
+
expect(cells.map(&:length)).to all(eq(size))
|
|
1187
|
+
expect(cells.flatten.map { |cell| [cell[:foreground], cell[:background]] }.uniq).to eq([%i[white black]])
|
|
1188
|
+
points = []
|
|
1189
|
+
cells.each_with_index do |row, y|
|
|
1190
|
+
row.each_with_index do |cell, x|
|
|
1191
|
+
next if cell[:glyph] == ' '
|
|
1192
|
+
|
|
1193
|
+
mask = cell[:glyph] == ' ' ? 0 : cell[:glyph].ord - 0x2800
|
|
1194
|
+
expect(mask).not_to be_nil
|
|
1195
|
+
[[0, 0, 1], [0, 1, 2], [0, 2, 4], [0, 3, 64], [1, 0, 8], [1, 1, 16], [1, 2, 32], [1, 3, 128]].each do |dx, dy, bit|
|
|
1196
|
+
points << [(x * 2) + dx, (y * 4) + dy] if mask.anybits?(bit)
|
|
1197
|
+
end
|
|
1198
|
+
end
|
|
1199
|
+
end
|
|
1200
|
+
expect(points).not_to be_empty
|
|
1201
|
+
next if size < 8
|
|
1202
|
+
|
|
1203
|
+
xs, ys = points.transpose
|
|
1204
|
+
ratio = console.rabbit_source.map(&:length).max.fdiv(console.rabbit_source.length * 2)
|
|
1205
|
+
expect((xs.max - xs.min + 1).fdiv(ys.max - ys.min + 1)).to be_within(0.1).of(ratio)
|
|
1206
|
+
expect((xs.min - ((size * 2) - 1 - xs.max)).abs).to be <= 2
|
|
1207
|
+
expect((ys.min - ((size * 4) - 1 - ys.max)).abs).to be <= 2
|
|
1208
|
+
expect(points.length).to be > size * 3
|
|
1209
|
+
expect(cells.flatten.map { |cell| cell[:glyph] }.uniq.length).to be > 4
|
|
1210
|
+
expect(console.banner_frame(size, cells: true)).to equal(cells)
|
|
1211
|
+
end
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
it 'paints only white with a 64-pair palette and keeps rabbit geometry in monochrome' do
|
|
1215
|
+
[false, true].each do |no_color|
|
|
1216
|
+
curses = double(has_colors?: true, start_color: nil, use_default_colors: nil, init_pair: nil, color_pairs: 64)
|
|
1217
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: curses, getch: nil)
|
|
1218
|
+
allow(ENV).to receive(:key?).with('NO_COLOR').and_return(no_color)
|
|
1219
|
+
allow(PWN::Banner).to receive(:mini_names).and_return([:falling_blocks])
|
|
1220
|
+
console.setup_colors
|
|
1221
|
+
painted = []
|
|
1222
|
+
allow(console).to receive(:put) { |*args| painted << args }
|
|
1223
|
+
console.draw_banner(8)
|
|
1224
|
+
ink = painted.reject { |_, _, glyph, _| glyph.strip.empty? }
|
|
1225
|
+
expect(ink).not_to be_empty
|
|
1226
|
+
white = 9 + PWN::Plugins::REPL::AIConsole::Console::PALETTE.index('white')
|
|
1227
|
+
expect(ink.map(&:last).uniq).to eq([no_color ? nil : white])
|
|
1228
|
+
expect(ink.map { |_, _, glyph, _| glyph }).to all(match(/[\u2801-\u28ff]/))
|
|
1229
|
+
end
|
|
1230
|
+
end
|
|
1231
|
+
|
|
1232
|
+
it 'activates once on actual focus, cycles games and never restores the splash on return' do
|
|
1233
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1234
|
+
allow(PWN::Banner).to receive(:mini_names).and_return(%i[snake pacman])
|
|
1235
|
+
allow(Process).to receive(:clock_gettime).and_return(10.0)
|
|
1236
|
+
splash = console.banner_frame(8, cells: true)
|
|
1237
|
+
console.instance_variable_set(:@header_pane_height, 10)
|
|
1238
|
+
2.times { console.handle("\u0014") }
|
|
1239
|
+
console.banner_frame(8, cells: true)
|
|
1240
|
+
game = console.instance_variable_get(:@banner_game)
|
|
1241
|
+
expect(game).to be_a(PWN::Banner::MiniGame)
|
|
1242
|
+
console.handle("\e")
|
|
1243
|
+
expect(console.banner_frame(8, cells: true)).not_to eq(splash)
|
|
1244
|
+
2.times { console.handle("\u0018") }
|
|
1245
|
+
console.banner_frame(8, cells: true)
|
|
1246
|
+
expect(console.instance_variable_get(:@banner_game)).to equal(game)
|
|
1247
|
+
name = console.instance_variable_get(:@banner_name)
|
|
1248
|
+
console.handle("\u0007")
|
|
1249
|
+
console.banner_frame(8, cells: true)
|
|
1250
|
+
expect(console.instance_variable_get(:@banner_name)).not_to eq(name)
|
|
1251
|
+
expect(console.instance_variable_get(:@banner_game)).not_to equal(game)
|
|
1252
|
+
console.handle("\e")
|
|
1253
|
+
expect(console.banner_frame(12, cells: true)).not_to eq(console.rabbit_cells(12))
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
it 'does not activate games when the pane is hidden or focus keys belong to a modal' do
|
|
1257
|
+
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1258
|
+
expect(PWN::Banner::MiniGame).not_to receive(:new)
|
|
1259
|
+
4.times { console.handle("\u0014") }
|
|
1260
|
+
expect(console.instance_variable_get(:@banner_activated)).to be_nil
|
|
1261
|
+
console.instance_variable_set(:@header_pane_height, 10)
|
|
1262
|
+
console.instance_variable_set(:@details, 0)
|
|
1263
|
+
expect(console).to receive(:handle_details).with("\u0018")
|
|
1264
|
+
console.handle("\u0018")
|
|
1265
|
+
expect(console.banner_frame(8, cells: true)).to eq(console.rabbit_cells(8))
|
|
1266
|
+
end
|
|
1267
|
+
|
|
1123
1268
|
it 'cycles mission, session and visible animation with both shortcuts without changing the draft' do
|
|
1124
1269
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1125
1270
|
editor = console.instance_variable_get(:@editor)
|
|
@@ -1249,6 +1394,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
1249
1394
|
it 'returns to the unchanged automatic animation when focus leaves the game' do
|
|
1250
1395
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1251
1396
|
allow(Process).to receive(:clock_gettime).and_return(10.0)
|
|
1397
|
+
console.instance_variable_set(:@banner_activated, true)
|
|
1252
1398
|
automatic = console.banner_frame(8, cells: true)
|
|
1253
1399
|
console.instance_variable_set(:@header_pane_height, 10)
|
|
1254
1400
|
2.times { console.handle("\u0014") }
|
|
@@ -1263,6 +1409,7 @@ describe 'pwn-ai curses launch' do # rubocop:disable Metrics/BlockLength -- publ
|
|
|
1263
1409
|
it 'streams the new arcade demos on the UI owner without a synchronous full replay build' do
|
|
1264
1410
|
%i[pacman galaga frogger].each do |name|
|
|
1265
1411
|
console = PWN::Plugins::REPL::AIConsole::Console.new(pry: Pry.new, input: StringIO.new, curses: nil, getch: nil)
|
|
1412
|
+
console.instance_variable_set(:@banner_activated, true)
|
|
1266
1413
|
allow(PWN::Banner).to receive(:mini_names).and_return([name])
|
|
1267
1414
|
allow(Process).to receive(:clock_gettime).and_return(10.0)
|
|
1268
1415
|
expect(PWN::Banner).not_to receive(:mini_cells)
|
data/third_party/pwn_rdoc.jsonl
CHANGED
|
@@ -3405,6 +3405,9 @@
|
|
|
3405
3405
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.prepare_swarm_mission Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.prepare_swarm_mission`: "}]}
|
|
3406
3406
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.put Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.put`: "}]}
|
|
3407
3407
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.put_spans Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.put_spans`: "}]}
|
|
3408
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.rabbit_cells Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.rabbit_cells`: "}]}
|
|
3409
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.rabbit_coverage Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.rabbit_coverage`: "}]}
|
|
3410
|
+
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.rabbit_source Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.rabbit_source`: "}]}
|
|
3408
3411
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.refresh_completion Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.refresh_completion`: "}]}
|
|
3409
3412
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.refresh_swarm Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.refresh_swarm`: "}]}
|
|
3410
3413
|
{"messages":[{"role":"user","content":"PWN::Plugins::REPL::AIConsole::Console.restore_streams Usage"},{"role":"assistant","content":"`PWN::Plugins::REPL::AIConsole::Console.restore_streams`: "}]}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pwn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.763
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 0day Inc.
|
|
@@ -897,14 +897,14 @@ dependencies:
|
|
|
897
897
|
requirements:
|
|
898
898
|
- - '='
|
|
899
899
|
- !ruby/object:Gem::Version
|
|
900
|
-
version: 7.1.
|
|
900
|
+
version: 7.1.7
|
|
901
901
|
type: :development
|
|
902
902
|
prerelease: false
|
|
903
903
|
version_requirements: !ruby/object:Gem::Requirement
|
|
904
904
|
requirements:
|
|
905
905
|
- - '='
|
|
906
906
|
- !ruby/object:Gem::Version
|
|
907
|
-
version: 7.1.
|
|
907
|
+
version: 7.1.7
|
|
908
908
|
- !ruby/object:Gem::Dependency
|
|
909
909
|
name: rqrcode
|
|
910
910
|
requirement: !ruby/object:Gem::Requirement
|