Script Panels
Scripts can claim their own real display panel now, instead of only ever printing lines and reading one at a time - open_panel() grabs one from a script panel in your layout.cfg. The Layout Designer can build one visually and export the file.
open_panel("log") claims a plain scrolling text panel instead of a character grid - just for a script's own log() output, so it can run with "visuals" without touching a single term_* function.
Once you've got a panel, term_write()/term_color()/term_move()/term_clear()/term_bg()/term_size() draw to a real character grid - your own colors, your own layout, cell by cell.
term_box()/term_hline()/term_vline() draw borders and lines for you, and term_fill() colors a rectangle instead of the whole panel.
term_click_zone() declares a clickable region without drawing anything itself - get_click() now returns which declared zone (if any) a click landed in.
term_scale() draws bigger text for a title or header, term_clear_rect() blanks just part of a panel, and term_cursor() hides the cursor block for a purely graphical display.
get_click()/get_key() poll for live mouse and keyboard input from inside your own loop - a script can react to a click or a keypress the moment it happens, not just read a line of typed text after the fact.
get_event() polls clicks and keys off one shared, ordered queue instead of two separate ones - useful the moment a panel has more than one clickable field, so a click that changes which field has focus and the keystrokes typed right after it always come back in the order they actually happened. get_click()/get_key() still work fine for a panel that only cares about one or the other.
Drawing and polling run at their own fast, fixed pace, completely separate from your CPU tier - none of it counts against the tick economy real hacking work costs against.
A script's reactive logic - deciding what to draw in response to a click or keypress, not just the drawing itself - can now live inside a render: block and run at that same free, fixed pace.
The terminal can be tabbed alongside other panes in layout.cfg now too, instead of always needing a slot to itself.
There are a couple of working examples of what all this can do already sitting out there in-game, if you go looking for them. A barebones one - just enough to see the shape of it - is up on the API reference too, alongside the rest of the docs at blackdoor.tools/docs.
CPU & Performance
A community bug report did real controlled testing across a dozen-plus isolated scripts to chase down a "CPU feels inconsistent" symptom to its actual root causes - genuinely excellent work, and worth a section of its own rather than getting buried in the usual bug-fix list.
The overload kill is supposed to fire after 5 seconds sustained at 100%+, but could instead fire anywhere from well under a minute to nearly twice that for the exact same maxed-out workload, run to run. The timer was ticking on your screen's own render rate instead of a fixed clock - a slower machine, or a minimized/unfocused window, could get killed well before it had actually earned it. It's on a fixed clock now, so the same workload behaves the same way every time.
get_cpu() could keep reporting a maxed-out reading for a while after your processes had actually already stopped, and gave inconsistent numbers if you polled it several times in a row - it was reading a smoothed display value instead of your real, live usage. It now always matches what's actually running right now.
Logging a concatenated string - log("progress: " + str(x)) and the like - quietly cost an extra CPU tick over logging a plain value. Fixed - concatenating inside a log() call is free again, same as everywhere else in the language.
get_unix_time()'s docs said it returns an int; it's always actually returned a float with real sub-second precision. Docs now match reality.
Bug Fixes
A @param default written in quotes (default="5") kept the quote characters as part of the string. Writing it without quotes (default=5) dodged that but got silently treated as a number even where a string was expected. Quoting a default now works the same way it does everywhere else in the language.
Cracking an account on an IDS-enabled server could rotate that account's own password the instant you finished cracking it - before you'd done anything with the access. Fixed - repeated failed login attempts still raise suspicion, cracking one on its own no longer does.
mount's /mnt sync only ever worked one way - a real file changed on disk would show up in-game, but editing, copying, moving, renaming, deleting, or creating a /mnt file in-game never touched the real file on disk at all. Fixed - all of that now writes straight through to the real file, same as an external editor's own save already did.
import "name" never looked inside /mnt at all, even though run could - a module sitting right next to the script importing it could fail to import just because it lived in /mnt. Fixed.
Crack Source's delivered script and docs read like it should match crack()'s speed - it can't, since interpreted .bd code costs real ticks a native function doesn't, and it would CPU-kill before finding a single character. The delivered script now disconnects right after grabbing the hash instead of at the very end, and the wording now leads with the actual point: nothing past that first hash grab needs the connection, so you can search offline instead of holding a live session open.
The daily FTP-focused and database-focused CTF boxes had no way to discover the username needed to get past the first flag - neither one has a root account, and nothing on either box ever revealed the real one. Fixed - it's now findable the same way the first flag already is.
An open FTP or database session could occasionally get treated as if it wasn't there - a typed command misfiring, or the connection appearing to drop - if something unrelated happened in the background at the same moment (a script finishing, erroring, or getting killed elsewhere). Fixed.
remote_run() (Pivot Source) and pivot() (ProxyChain) share "pivot" branding but do genuinely different things - remote_run() is a reachability tool only, it never masked your IP from a trace despite reading like it might. Its docs (in-game and on the site) now say so plainly instead of leaving it to infer.
Reputation could get ground all the way down to 0 by repeated HUNTED penalties, and nearly every mission past the opening tutorial needs at least 1 - a genuine dead end with nothing left to accept. Fixed - a "Quick Favour" job is now offered the moment you're below your best-ever reputation, so there's always a way back up even from 0.
Workshop content could still reset its visibility to public after republishing in some cases, even with the earlier fix for this in place. Fixed - your chosen visibility now survives every republish path.
Subscribed Workshop content could keep loading an outdated version after the creator pushed an update, instead of the current one. Fixed - it now checks for and grabs the latest version before loading, and the Workshop import list updates live the moment a subscription changes instead of needing a restart to notice.
If you'd ever edited a file on a Workshop server yourself (e.g. live-debugging a script through SSH), that edit could keep silently overriding the creator's own updates forever after - a republish with new content, or newly-enabled encryption, at that same file just wouldn't take. Deleted files could also reappear the same way. Fixed - your own edits still persist as before, but only for files the creator hasn't touched since; their own updates now always win where they've actually changed something.
Also New
cp supports a -r flag now, matching rm - copy a whole directory at once instead of one file at a time.
A bare run now finds a script anywhere under /mnt, not just sitting directly at its top level - run foo finds /mnt/lib/foo.bd the same as it already found /mnt/foo.bd.
Where a bare script name gets looked up is configurable now - blackdoor.conf's new path= line takes a comma-separated list of directories (defaults to ~/scripts,/mnt if you don't set one). which shows exactly where a name currently resolves to.
run script.bd > file (or >> file to append) sends everything a script logs to a file instead of the terminal - works in both foreground and background, and captures a real script error the same way.
Commands can be chained on one line now - cmd1 ; cmd2 runs each one in sequence, cmd1 && cmd2 only runs the second if the first succeeded, and cmd1 || cmd2 only runs it if the first failed.
remote_running(conn, script_path) (Pivot Source) checks whether a remote_run() is still going on the target, so a script can wait for one to finish before starting the next.
The files pane has a "run" action now for any .bd script, right alongside edit/read/delete - no need to type run by hand just to try something you're already looking at.
netcat() challenge ports aren't limited to the built-in string-reversal puzzle anymore - a "challenge" port can now set challenge_type to math (a random arithmetic problem), fixed (your own author-written question/answer pairs), or script (a real .bd file on the server that computes its own question and answer, with the full language available to it). A new challenge_begin event also fires the moment someone connects, before the challenge itself is even generated.
The Workshop editor's port SERVICE field now shows which exact (case-sensitive) strings actually unlock real behavior - ssh, ftp, mysql/postgres/postgresql, http/https/http-alt, smtp, challenge. Anything else is fine for pure flavor, but a typo in one of these used to silently degrade a port to flavor-only with no error at all.
If your reputation dips below where it's been before, hexdump now offers a "Quick Favour" job to help close the gap - modest pay, but real reputation, until you're caught back up.
If a "script"-type challenge port's own .bd script has a bug, you'll now see the real error message if you're the Workshop author of that box (prefixed onto the same solvable fallback puzzle everyone else still gets) - so you can actually find and fix it instead of just wondering why it's using the fallback.
- Static
