Repository: dankamongmen/notcurses (language: python)

Issue:
smooth bitmap movement with sixel graphics
in #1682 we introduced the pixel-offset fields to `ncvisual_options`, and added support for Kitty graphics. we ought do the same for Sixel graphics. there we have no in-protocol support; we would need add the necessary transparency rows and columns to the image ourself, with substantially increased processing requirements. this might be post-3.0 material as a result.
valgrind reports leaks when using ncvisuals
I was trying to fix memleaks in the rust bindings when passing mutable strings to C and having interesting lifetime problems, but it turns out I was also getting errors when using ncvisuals, and when trying to debug them I found out valgrind also detects errors in the C code, like the `pixel`, and `interp` *pocs*.

@dankamongmen does it happen for you too? or is my system just a bit tad strange? 

`valgrind --leak-check=full -s BIN` to replicate
valgrind reports leaks when using ncvisuals
I was trying to fix memleaks in the rust bindings when passing mutable strings to C and having interesting lifetime problems, but it turns out I was also getting errors when using ncvisuals, and when trying to debug them I found out valgrind also detects errors in the C code, like the `pixel`, and `interp` *pocs*.

@dankamongmen does it happen for you too? or is my system just a bit tad strange? 

`valgrind --leak-check=full -s BIN` to replicate
constify strings in ncselector_item
@joseluis would appreciate it if we made the `char*`s in `ncselector_item` and `ncmultiselector_item` `const`, which is a reasonable ask. we'd just need introduce internal types that don't have the `const`, and use those for our storage. easy enough.
windows ersatz problems
Some things I've noticed recently working on windows:

* [x] `uniblock` is broken with "couldn't convert 256 (100) () (Illegal byte sequence)"
* [x] `notcurses-info` prints no glyphs on a line after any failed glyph, it seems
* [x] can we detect whether we're in a working mintty? i think so -- error out on windows if we can't get ConsoleHandles (see #2248)
* [x] `normal` fails on bad rotation characters 0xe2, happens on first rotation
* [x] `animate` fails
* [x] need pipe-like input notification
* [x] `[eagle]` fails somewhere in `ncvisual_destroy()`, and it's an immediate exit, not a true error

i'd like to have `notcurses-demo` working all the way through on windows soon.
windows ersatz problems
Some things I've noticed recently working on windows:

* [x] `uniblock` is broken with "couldn't convert 256 (100) () (Illegal byte sequence)"
* [x] `notcurses-info` prints no glyphs on a line after any failed glyph, it seems
* [x] can we detect whether we're in a working mintty? i think so -- error out on windows if we can't get ConsoleHandles (see #2248)
* [x] `normal` fails on bad rotation characters 0xe2, happens on first rotation
* [x] `animate` fails
* [x] need pipe-like input notification
* [x] `[eagle]` fails somewhere in `ncvisual_destroy()`, and it's an immediate exit, not a true error

i'd like to have `notcurses-demo` working all the way through on windows soon.
misidentifying mintty as windows terminal
When running `mintty -P on` in MSYS2, we identify the terminal as `TERM_MSTERMINAL`, due to it now properly acquiring the ConsoleHandles (OH SHIT this explains how to solve #2244 subissue 3 -- just *exit* on Windows if we can't get the handles!). That's an insufficient litmus now that @mintty has shown us the power of `mintty -P on`. Continue to run detection after acquiring the handles, and prefer MinTTY on a hard match. Fall back to MSTERMINAL.
valgrind reports leaks when using ncvisuals
I was trying to fix memleaks in the rust bindings when passing mutable strings to C and having interesting lifetime problems, but it turns out I was also getting errors when using ncvisuals, and when trying to debug them I found out valgrind also detects errors in the C code, like the `pixel`, and `interp` *pocs*.

@dankamongmen does it happen for you too? or is my system just a bit tad strange? 

`valgrind --leak-check=full -s BIN` to replicate
constify items reference in ncselector_item and ncmselector_item
This is a step more on top of #2267

I made some tests and it seems to work fine without needing to pass a mutable reference
ncvisual_render needs be pile-aware
When `ncvisual_render()` is called with a `ncvisual_options` containing a `NULL` value for `n` (i.e. the create a new plane case), the `y` and `x` fields are interpreted as origins of the new plane. Said plane will always be placed relative to the standard pile. We should be able to create a visual plane in a different pile. Sure, the user can always immediately move the plane, but this means `ncvisual_render()` cannot be used without a target plane by some other pile when the standard pile is being rendered (or any number of other things).

ncvisual_render needs be pile-aware
When `ncvisual_render()` is called with a `ncvisual_options` containing a `NULL` value for `n` (i.e. the create a new plane case), the `y` and `x` fields are interpreted as origins of the new plane. Said plane will always be placed relative to the standard pile. We should be able to create a visual plane in a different pile. Sure, the user can always immediately move the plane, but this means `ncvisual_render()` cannot be used without a target plane by some other pile when the standard pile is being rendered (or any number of other things).

clear all deprecated elements before calling it abi3
There's been a good deal of deprecated material collecting, which we can't remove until ABI3, intended to be marked by 3.0.0.

I've been trying to move it to the bottom of `notcurses.h` and `ncdirect.h`, but we ought do a final pass.

All shipped code ought already have moved off of deprecated material, of course.

Also look for anything we can move to a `static inline` and away from a symbol. Symbols can't be promoted to static inline, or else old programs will stop linking.

a partial list, growing as things change:

* [x] remove `renderfile` from `notcurses_options` #2081
* [x] convert all `notcurses_can_*()` functions to `static inline` wrappers around `notcurses_capabilities()` #1768 
* [x] convert all non-`inline` `ncdirect_can_*()` functions to `static inline`s around `ncdirect_capabilities()` #1768
* [x] kill all functions marked as `((deprecated))`
* [x] kill old `CELL_ALPHA_*` definitions
* [x] kill old `palette256` type
* [x] `notcurses_render_to_buffer()`,  `notcurses_render_to_file()`
* [x] `notcurses_render()` becomes `static inline`
* [x] `nccell_width()` is removed
* [x] move fields around in `ncstats` to match documentation
* [x] remove the unused third parameter from `ncdirect_getc()` and `notcurses_getc()` #1967
* [x] renumber our synthesized `NCKEY` constants to match the kitty protocol (see #2131)
* [x] `ncstrwidth` becomes `static inline` (see #2153)
* [x] remove definitions of `notcurses_getc()` and `ncdirect_getc()`
* [x] stylemask functions ought return `uint16_t` (see #2200)
* [x] `ncplane_move_below()` and `ncplane_move_above()` ought be modified to accept `NULL`, with obvious implications, and `ncplane_move_top()` and `ncplane_move_bottom()` can be rewritten as `static inlines` around them, cutting two symbols
* [x] rename `ncplane_pixelgeom` to `ncplane_pixel_geom`
* [x] rename `ncplane_moverel` to `ncplane_move_rel`
* [x] change all geometry params and especially returns from `int` to `unsigned`
* [x] require `ncpile*` (from `ncplane*`) instead of `notcurses*` for `ncvisual_render()`, `NULL` for new pile (#1462)
* [x] `notcurses_get()` ought take an absolute deadline rather than a delay bound. kill off `delaybound_to_deadline()` once this is done (see #2302)
* [x] `ncdirect_rounded_box()` and `ncdirect_double_box()` ought become `static inline`
* [x] `notcurses_top()` and `notcurses_bottom()` can become static inlines around `ncpile_{top, bottom}()`
* [x] `ncmetric()` can become `static inline`

you know, maybe we don't want to nuke everything that's deprecated; where possible, we could keep them as (deprecated) `static inline`s, so a recompile still works. maybe.

rejected:

*~~`ncplane_erase()` becomes static inline around `ncplane_erase_region()`, which calls the current internal `ncplane_erase()` for that special case (it's a good optimization)~~ (dumb idea. this would just require traversing all the checks in `ncplane_erase_region()` for no good reason)
* ~~`notcurses_getvec()` can become static inline~~ the whole point of `notcurses_getvec()` is speed, and we can be faster if we embed this behind the locking
clear all deprecated elements before calling it abi3
There's been a good deal of deprecated material collecting, which we can't remove until ABI3, intended to be marked by 3.0.0.

I've been trying to move it to the bottom of `notcurses.h` and `ncdirect.h`, but we ought do a final pass.

All shipped code ought already have moved off of deprecated material, of course.

Also look for anything we can move to a `static inline` and away from a symbol. Symbols can't be promoted to static inline, or else old programs will stop linking.

a partial list, growing as things change:

* [x] remove `renderfile` from `notcurses_options` #2081
* [x] convert all `notcurses_can_*()` functions to `static inline` wrappers around `notcurses_capabilities()` #1768 
* [x] convert all non-`inline` `ncdirect_can_*()` functions to `static inline`s around `ncdirect_capabilities()` #1768
* [x] kill all functions marked as `((deprecated))`
* [x] kill old `CELL_ALPHA_*` definitions
* [x] kill old `palette256` type
* [x] `notcurses_render_to_buffer()`,  `notcurses_render_to_file()`
* [x] `notcurses_render()` becomes `static inline`
* [x] `nccell_width()` is removed
* [x] move fields around in `ncstats` to match documentation
* [x] remove the unused third parameter from `ncdirect_getc()` and `notcurses_getc()` #1967
* [x] renumber our synthesized `NCKEY` constants to match the kitty protocol (see #2131)
* [x] `ncstrwidth` becomes `static inline` (see #2153)
* [x] remove definitions of `notcurses_getc()` and `ncdirect_getc()`
* [x] stylemask functions ought return `uint16_t` (see #2200)
* [x] `ncplane_move_below()` and `ncplane_move_above()` ought be modified to accept `NULL`, with obvious implications, and `ncplane_move_top()` and `ncplane_move_bottom()` can be rewritten as `static inlines` around them, cutting two symbols
* [x] rename `ncplane_pixelgeom` to `ncplane_pixel_geom`
* [x] rename `ncplane_moverel` to `ncplane_move_rel`
* [x] change all geometry params and especially returns from `int` to `unsigned`
* [x] require `ncpile*` (from `ncplane*`) instead of `notcurses*` for `ncvisual_render()`, `NULL` for new pile (#1462)
* [x] `notcurses_get()` ought take an absolute deadline rather than a delay bound. kill off `delaybound_to_deadline()` once this is done (see #2302)
* [x] `ncdirect_rounded_box()` and `ncdirect_double_box()` ought become `static inline`
* [x] `notcurses_top()` and `notcurses_bottom()` can become static inlines around `ncpile_{top, bottom}()`
* [x] `ncmetric()` can become `static inline`

you know, maybe we don't want to nuke everything that's deprecated; where possible, we could keep them as (deprecated) `static inline`s, so a recompile still works. maybe.

rejected:

*~~`ncplane_erase()` becomes static inline around `ncplane_erase_region()`, which calls the current internal `ncplane_erase()` for that special case (it's a good optimization)~~ (dumb idea. this would just require traversing all the checks in `ncplane_erase_region()` for no good reason)
* ~~`notcurses_getvec()` can become static inline~~ the whole point of `notcurses_getvec()` is speed, and we can be faster if we embed this behind the locking
clear all deprecated elements before calling it abi3
There's been a good deal of deprecated material collecting, which we can't remove until ABI3, intended to be marked by 3.0.0.

I've been trying to move it to the bottom of `notcurses.h` and `ncdirect.h`, but we ought do a final pass.

All shipped code ought already have moved off of deprecated material, of course.

Also look for anything we can move to a `static inline` and away from a symbol. Symbols can't be promoted to static inline, or else old programs will stop linking.

a partial list, growing as things change:

* [x] remove `renderfile` from `notcurses_options` #2081
* [x] convert all `notcurses_can_*()` functions to `static inline` wrappers around `notcurses_capabilities()` #1768 
* [x] convert all non-`inline` `ncdirect_can_*()` functions to `static inline`s around `ncdirect_capabilities()` #1768
* [x] kill all functions marked as `((deprecated))`
* [x] kill old `CELL_ALPHA_*` definitions
* [x] kill old `palette256` type
* [x] `notcurses_render_to_buffer()`,  `notcurses_render_to_file()`
* [x] `notcurses_render()` becomes `static inline`
* [x] `nccell_width()` is removed
* [x] move fields around in `ncstats` to match documentation
* [x] remove the unused third parameter from `ncdirect_getc()` and `notcurses_getc()` #1967
* [x] renumber our synthesized `NCKEY` constants to match the kitty protocol (see #2131)
* [x] `ncstrwidth` becomes `static inline` (see #2153)
* [x] remove definitions of `notcurses_getc()` and `ncdirect_getc()`
* [x] stylemask functions ought return `uint16_t` (see #2200)
* [x] `ncplane_move_below()` and `ncplane_move_above()` ought be modified to accept `NULL`, with obvious implications, and `ncplane_move_top()` and `ncplane_move_bottom()` can be rewritten as `static inlines` around them, cutting two symbols
* [x] rename `ncplane_pixelgeom` to `ncplane_pixel_geom`
* [x] rename `ncplane_moverel` to `ncplane_move_rel`
* [x] change all geometry params and especially returns from `int` to `unsigned`
* [x] require `ncpile*` (from `ncplane*`) instead of `notcurses*` for `ncvisual_render()`, `NULL` for new pile (#1462)
* [x] `notcurses_get()` ought take an absolute deadline rather than a delay bound. kill off `delaybound_to_deadline()` once this is done (see #2302)
* [x] `ncdirect_rounded_box()` and `ncdirect_double_box()` ought become `static inline`
* [x] `notcurses_top()` and `notcurses_bottom()` can become static inlines around `ncpile_{top, bottom}()`
* [x] `ncmetric()` can become `static inline`

you know, maybe we don't want to nuke everything that's deprecated; where possible, we could keep them as (deprecated) `static inline`s, so a recompile still works. maybe.

rejected:

*~~`ncplane_erase()` becomes static inline around `ncplane_erase_region()`, which calls the current internal `ncplane_erase()` for that special case (it's a good optimization)~~ (dumb idea. this would just require traversing all the checks in `ncplane_erase_region()` for no good reason)
* ~~`notcurses_getvec()` can become static inline~~ the whole point of `notcurses_getvec()` is speed, and we can be faster if we embed this behind the locking
clear all deprecated elements before calling it abi3
There's been a good deal of deprecated material collecting, which we can't remove until ABI3, intended to be marked by 3.0.0.

I've been trying to move it to the bottom of `notcurses.h` and `ncdirect.h`, but we ought do a final pass.

All shipped code ought already have moved off of deprecated material, of course.

Also look for anything we can move to a `static inline` and away from a symbol. Symbols can't be promoted to static inline, or else old programs will stop linking.

a partial list, growing as things change:

* [x] remove `renderfile` from `notcurses_options` #2081
* [x] convert all `notcurses_can_*()` functions to `static inline` wrappers around `notcurses_capabilities()` #1768 
* [x] convert all non-`inline` `ncdirect_can_*()` functions to `static inline`s around `ncdirect_capabilities()` #1768
* [x] kill all functions marked as `((deprecated))`
* [x] kill old `CELL_ALPHA_*` definitions
* [x] kill old `palette256` type
* [x] `notcurses_render_to_buffer()`,  `notcurses_render_to_file()`
* [x] `notcurses_render()` becomes `static inline`
* [x] `nccell_width()` is removed
* [x] move fields around in `ncstats` to match documentation
* [x] remove the unused third parameter from `ncdirect_getc()` and `notcurses_getc()` #1967
* [x] renumber our synthesized `NCKEY` constants to match the kitty protocol (see #2131)
* [x] `ncstrwidth` becomes `static inline` (see #2153)
* [x] remove definitions of `notcurses_getc()` and `ncdirect_getc()`
* [x] stylemask functions ought return `uint16_t` (see #2200)
* [x] `ncplane_move_below()` and `ncplane_move_above()` ought be modified to accept `NULL`, with obvious implications, and `ncplane_move_top()` and `ncplane_move_bottom()` can be rewritten as `static inlines` around them, cutting two symbols
* [x] rename `ncplane_pixelgeom` to `ncplane_pixel_geom`
* [x] rename `ncplane_moverel` to `ncplane_move_rel`
* [x] change all geometry params and especially returns from `int` to `unsigned`
* [x] require `ncpile*` (from `ncplane*`) instead of `notcurses*` for `ncvisual_render()`, `NULL` for new pile (#1462)
* [x] `notcurses_get()` ought take an absolute deadline rather than a delay bound. kill off `delaybound_to_deadline()` once this is done (see #2302)
* [x] `ncdirect_rounded_box()` and `ncdirect_double_box()` ought become `static inline`
* [x] `notcurses_top()` and `notcurses_bottom()` can become static inlines around `ncpile_{top, bottom}()`
* [x] `ncmetric()` can become `static inline`

you know, maybe we don't want to nuke everything that's deprecated; where possible, we could keep them as (deprecated) `static inline`s, so a recompile still works. maybe.

rejected:

*~~`ncplane_erase()` becomes static inline around `ncplane_erase_region()`, which calls the current internal `ncplane_erase()` for that special case (it's a good optimization)~~ (dumb idea. this would just require traversing all the checks in `ncplane_erase_region()` for no good reason)
* ~~`notcurses_getvec()` can become static inline~~ the whole point of `notcurses_getvec()` is speed, and we can be faster if we embed this behind the locking
clear all deprecated elements before calling it abi3
There's been a good deal of deprecated material collecting, which we can't remove until ABI3, intended to be marked by 3.0.0.

I've been trying to move it to the bottom of `notcurses.h` and `ncdirect.h`, but we ought do a final pass.

All shipped code ought already have moved off of deprecated material, of course.

Also look for anything we can move to a `static inline` and away from a symbol. Symbols can't be promoted to static inline, or else old programs will stop linking.

a partial list, growing as things change:

* [x] remove `renderfile` from `notcurses_options` #2081
* [x] convert all `notcurses_can_*()` functions to `static inline` wrappers around `notcurses_capabilities()` #1768 
* [x] convert all non-`inline` `ncdirect_can_*()` functions to `static inline`s around `ncdirect_capabilities()` #1768
* [x] kill all functions marked as `((deprecated))`
* [x] kill old `CELL_ALPHA_*` definitions
* [x] kill old `palette256` type
* [x] `notcurses_render_to_buffer()`,  `notcurses_render_to_file()`
* [x] `notcurses_render()` becomes `static inline`
* [x] `nccell_width()` is removed
* [x] move fields around in `ncstats` to match documentation
* [x] remove the unused third parameter from `ncdirect_getc()` and `notcurses_getc()` #1967
* [x] renumber our synthesized `NCKEY` constants to match the kitty protocol (see #2131)
* [x] `ncstrwidth` becomes `static inline` (see #2153)
* [x] remove definitions of `notcurses_getc()` and `ncdirect_getc()`
* [x] stylemask functions ought return `uint16_t` (see #2200)
* [x] `ncplane_move_below()` and `ncplane_move_above()` ought be modified to accept `NULL`, with obvious implications, and `ncplane_move_top()` and `ncplane_move_bottom()` can be rewritten as `static inlines` around them, cutting two symbols
* [x] rename `ncplane_pixelgeom` to `ncplane_pixel_geom`
* [x] rename `ncplane_moverel` to `ncplane_move_rel`
* [x] change all geometry params and especially returns from `int` to `unsigned`
* [x] require `ncpile*` (from `ncplane*`) instead of `notcurses*` for `ncvisual_render()`, `NULL` for new pile (#1462)
* [x] `notcurses_get()` ought take an absolute deadline rather than a delay bound. kill off `delaybound_to_deadline()` once this is done (see #2302)
* [x] `ncdirect_rounded_box()` and `ncdirect_double_box()` ought become `static inline`
* [x] `notcurses_top()` and `notcurses_bottom()` can become static inlines around `ncpile_{top, bottom}()`
* [x] `ncmetric()` can become `static inline`

you know, maybe we don't want to nuke everything that's deprecated; where possible, we could keep them as (deprecated) `static inline`s, so a recompile still works. maybe.

rejected:

*~~`ncplane_erase()` becomes static inline around `ncplane_erase_region()`, which calls the current internal `ncplane_erase()` for that special case (it's a good optimization)~~ (dumb idea. this would just require traversing all the checks in `ncplane_erase_region()` for no good reason)
* ~~`notcurses_getvec()` can become static inline~~ the whole point of `notcurses_getvec()` is speed, and we can be faster if we embed this behind the locking
clear all deprecated elements before calling it abi3
There's been a good deal of deprecated material collecting, which we can't remove until ABI3, intended to be marked by 3.0.0.

I've been trying to move it to the bottom of `notcurses.h` and `ncdirect.h`, but we ought do a final pass.

All shipped code ought already have moved off of deprecated material, of course.

Also look for anything we can move to a `static inline` and away from a symbol. Symbols can't be promoted to static inline, or else old programs will stop linking.

a partial list, growing as things change:

* [x] remove `renderfile` from `notcurses_options` #2081
* [x] convert all `notcurses_can_*()` functions to `static inline` wrappers around `notcurses_capabilities()` #1768 
* [x] convert all non-`inline` `ncdirect_can_*()` functions to `static inline`s around `ncdirect_capabilities()` #1768
* [x] kill all functions marked as `((deprecated))`
* [x] kill old `CELL_ALPHA_*` definitions
* [x] kill old `palette256` type
* [x] `notcurses_render_to_buffer()`,  `notcurses_render_to_file()`
* [x] `notcurses_render()` becomes `static inline`
* [x] `nccell_width()` is removed
* [x] move fields around in `ncstats` to match documentation
* [x] remove the unused third parameter from `ncdirect_getc()` and `notcurses_getc()` #1967
* [x] renumber our synthesized `NCKEY` constants to match the kitty protocol (see #2131)
* [x] `ncstrwidth` becomes `static inline` (see #2153)
* [x] remove definitions of `notcurses_getc()` and `ncdirect_getc()`
* [x] stylemask functions ought return `uint16_t` (see #2200)
* [x] `ncplane_move_below()` and `ncplane_move_above()` ought be modified to accept `NULL`, with obvious implications, and `ncplane_move_top()` and `ncplane_move_bottom()` can be rewritten as `static inlines` around them, cutting two symbols
* [x] rename `ncplane_pixelgeom` to `ncplane_pixel_geom`
* [x] rename `ncplane_moverel` to `ncplane_move_rel`
* [x] change all geometry params and especially returns from `int` to `unsigned`
* [x] require `ncpile*` (from `ncplane*`) instead of `notcurses*` for `ncvisual_render()`, `NULL` for new pile (#1462)
* [x] `notcurses_get()` ought take an absolute deadline rather than a delay bound. kill off `delaybound_to_deadline()` once this is done (see #2302)
* [x] `ncdirect_rounded_box()` and `ncdirect_double_box()` ought become `static inline`
* [x] `notcurses_top()` and `notcurses_bottom()` can become static inlines around `ncpile_{top, bottom}()`
* [x] `ncmetric()` can become `static inline`

you know, maybe we don't want to nuke everything that's deprecated; where possible, we could keep them as (deprecated) `static inline`s, so a recompile still works. maybe.

rejected:

*~~`ncplane_erase()` becomes static inline around `ncplane_erase_region()`, which calls the current internal `ncplane_erase()` for that special case (it's a good optimization)~~ (dumb idea. this would just require traversing all the checks in `ncplane_erase_region()` for no good reason)
* ~~`notcurses_getvec()` can become static inline~~ the whole point of `notcurses_getvec()` is speed, and we can be faster if we embed this behind the locking
clear all deprecated elements before calling it abi3
There's been a good deal of deprecated material collecting, which we can't remove until ABI3, intended to be marked by 3.0.0.

I've been trying to move it to the bottom of `notcurses.h` and `ncdirect.h`, but we ought do a final pass.

All shipped code ought already have moved off of deprecated material, of course.

Also look for anything we can move to a `static inline` and away from a symbol. Symbols can't be promoted to static inline, or else old programs will stop linking.

a partial list, growing as things change:

* [x] remove `renderfile` from `notcurses_options` #2081
* [x] convert all `notcurses_can_*()` functions to `static inline` wrappers around `notcurses_capabilities()` #1768 
* [x] convert all non-`inline` `ncdirect_can_*()` functions to `static inline`s around `ncdirect_capabilities()` #1768
* [x] kill all functions marked as `((deprecated))`
* [x] kill old `CELL_ALPHA_*` definitions
* [x] kill old `palette256` type
* [x] `notcurses_render_to_buffer()`,  `notcurses_render_to_file()`
* [x] `notcurses_render()` becomes `static inline`
* [x] `nccell_width()` is removed
* [x] move fields around in `ncstats` to match documentation
* [x] remove the unused third parameter from `ncdirect_getc()` and `notcurses_getc()` #1967
* [x] renumber our synthesized `NCKEY` constants to match the kitty protocol (see #2131)
* [x] `ncstrwidth` becomes `static inline` (see #2153)
* [x] remove definitions of `notcurses_getc()` and `ncdirect_getc()`
* [x] stylemask functions ought return `uint16_t` (see #2200)
* [x] `ncplane_move_below()` and `ncplane_move_above()` ought be modified to accept `NULL`, with obvious implications, and `ncplane_move_top()` and `ncplane_move_bottom()` can be rewritten as `static inlines` around them, cutting two symbols
* [x] rename `ncplane_pixelgeom` to `ncplane_pixel_geom`
* [x] rename `ncplane_moverel` to `ncplane_move_rel`
* [x] change all geometry params and especially returns from `int` to `unsigned`
* [x] require `ncpile*` (from `ncplane*`) instead of `notcurses*` for `ncvisual_render()`, `NULL` for new pile (#1462)
* [x] `notcurses_get()` ought take an absolute deadline rather than a delay bound. kill off `delaybound_to_deadline()` once this is done (see #2302)
* [x] `ncdirect_rounded_box()` and `ncdirect_double_box()` ought become `static inline`
* [x] `notcurses_top()` and `notcurses_bottom()` can become static inlines around `ncpile_{top, bottom}()`
* [x] `ncmetric()` can become `static inline`

you know, maybe we don't want to nuke everything that's deprecated; where possible, we could keep them as (deprecated) `static inline`s, so a recompile still works. maybe.

rejected:

*~~`ncplane_erase()` becomes static inline around `ncplane_erase_region()`, which calls the current internal `ncplane_erase()` for that special case (it's a good optimization)~~ (dumb idea. this would just require traversing all the checks in `ncplane_erase_region()` for no good reason)
* ~~`notcurses_getvec()` can become static inline~~ the whole point of `notcurses_getvec()` is speed, and we can be faster if we embed this behind the locking
clear all deprecated elements before calling it abi3
There's been a good deal of deprecated material collecting, which we can't remove until ABI3, intended to be marked by 3.0.0.

I've been trying to move it to the bottom of `notcurses.h` and `ncdirect.h`, but we ought do a final pass.

All shipped code ought already have moved off of deprecated material, of course.

Also look for anything we can move to a `static inline` and away from a symbol. Symbols can't be promoted to static inline, or else old programs will stop linking.

a partial list, growing as things change:

* [x] remove `renderfile` from `notcurses_options` #2081
* [x] convert all `notcurses_can_*()` functions to `static inline` wrappers around `notcurses_capabilities()` #1768 
* [x] convert all non-`inline` `ncdirect_can_*()` functions to `static inline`s around `ncdirect_capabilities()` #1768
* [x] kill all functions marked as `((deprecated))`
* [x] kill old `CELL_ALPHA_*` definitions
* [x] kill old `palette256` type
* [x] `notcurses_render_to_buffer()`,  `notcurses_render_to_file()`
* [x] `notcurses_render()` becomes `static inline`
* [x] `nccell_width()` is removed
* [x] move fields around in `ncstats` to match documentation
* [x] remove the unused third parameter from `ncdirect_getc()` and `notcurses_getc()` #1967
* [x] renumber our synthesized `NCKEY` constants to match the kitty protocol (see #2131)
* [x] `ncstrwidth` becomes `static inline` (see #2153)
* [x] remove definitions of `notcurses_getc()` and `ncdirect_getc()`
* [x] stylemask functions ought return `uint16_t` (see #2200)
* [x] `ncplane_move_below()` and `ncplane_move_above()` ought be modified to accept `NULL`, with obvious implications, and `ncplane_move_top()` and `ncplane_move_bottom()` can be rewritten as `static inlines` around them, cutting two symbols
* [x] rename `ncplane_pixelgeom` to `ncplane_pixel_geom`
* [x] rename `ncplane_moverel` to `ncplane_move_rel`
* [x] change all geometry params and especially returns from `int` to `unsigned`
* [x] require `ncpile*` (from `ncplane*`) instead of `notcurses*` for `ncvisual_render()`, `NULL` for new pile (#1462)
* [x] `notcurses_get()` ought take an absolute deadline rather than a delay bound. kill off `delaybound_to_deadline()` once this is done (see #2302)
* [x] `ncdirect_rounded_box()` and `ncdirect_double_box()` ought become `static inline`
* [x] `notcurses_top()` and `notcurses_bottom()` can become static inlines around `ncpile_{top, bottom}()`
* [x] `ncmetric()` can become `static inline`

you know, maybe we don't want to nuke everything that's deprecated; where possible, we could keep them as (deprecated) `static inline`s, so a recompile still works. maybe.

rejected:

*~~`ncplane_erase()` becomes static inline around `ncplane_erase_region()`, which calls the current internal `ncplane_erase()` for that special case (it's a good optimization)~~ (dumb idea. this would just require traversing all the checks in `ncplane_erase_region()` for no good reason)
* ~~`notcurses_getvec()` can become static inline~~ the whole point of `notcurses_getvec()` is speed, and we can be faster if we embed this behind the locking


Discussion / hints:
make sure we get enough progress on this here and in fbcon that the unit test no longer blows up when run there, as it is now.



`c32rtomb()` works to convert most of our `unicode` glyphs. `nl_langinfo()` indicates "UTF-8".
`c32rtomb()` works to convert most of our `unicode` glyphs. `nl_langinfo()` indicates "UTF-8".
Also, we probably want to use terminfo with MinTTY, as it works within MSYS2.
see #2245


Breaking the sprixel cache up among piles isn't hard, and indeed I'm about to check that in. The hard part is ensuring, say, that a sprixel cleaned up by the destruction of a pile gets turned into an actual kill, since it won't show up in the render cycle. Probably need to keep a context-wide list of those. Figure this out for 3.0.0.
one thing i'm pretty sure we need is to move the damage detection entiretly into rasterization. right now we do it primarily in rendering, but under the multipile model, the most recent frame (against which we must perform damage detection) isn't decided until we rasterize. we'll still need to set damage in the rendering phase for e.g. sprixel invalidations, but all `cellcmp_and_dupfar()` needs to take place during rasterization for sure. this is a major outstanding bug in our model, unless i'm completely missing something.
so the problem is down in `ncvisual_render_pixels()` and `ncvisual_render_cells()`, where we always go to `notcurses_stdplane()` for a `NULL` n. we need preserve that behavior in `ncvisual_render()` unless/until we remove it.

alright, let's introduce a new `ncvisual_blit()`, so to at least eliminate the confusion that can arise from `ncpile_render()` vs `ncvisual_render()`, and to fit in with `ncvisual_blitter_geom()`, and to change the semantics of a `NULL` `vopts->n`. prototype:

`struct ncplane* ncvisual_blit(struct ncplane* p, const struct ncvisual* ncv, const ncvisual_options *vopts);`

same as `ncvisual_render()`, actually. just the semantics change for `NULL` `vopts->n`; it now gets you a new pile. at the same time, we ought introduce code to `ncvisualplane_create()` to call `ncpile_create()` with a `NULL` n. which means we would need add `notcurses*nc` to its signature, but it's `static inline`, so we can do that freely.
I've made the switch to `ncvisualplane_create()`, and documented it in `NEWS.md`, in `master`. I'm making the other change in `dankamongmen/ncvisual_blit`.
Breaking the sprixel cache up among piles isn't hard, and indeed I'm about to check that in. The hard part is ensuring, say, that a sprixel cleaned up by the destruction of a pile gets turned into an actual kill, since it won't show up in the render cycle. Probably need to keep a context-wide list of those. Figure this out for 3.0.0.
one thing i'm pretty sure we need is to move the damage detection entiretly into rasterization. right now we do it primarily in rendering, but under the multipile model, the most recent frame (against which we must perform damage detection) isn't decided until we rasterize. we'll still need to set damage in the rendering phase for e.g. sprixel invalidations, but all `cellcmp_and_dupfar()` needs to take place during rasterization for sure. this is a major outstanding bug in our model, unless i'm completely missing something.
so the problem is down in `ncvisual_render_pixels()` and `ncvisual_render_cells()`, where we always go to `notcurses_stdplane()` for a `NULL` n. we need preserve that behavior in `ncvisual_render()` unless/until we remove it.

alright, let's introduce a new `ncvisual_blit()`, so to at least eliminate the confusion that can arise from `ncpile_render()` vs `ncvisual_render()`, and to fit in with `ncvisual_blitter_geom()`, and to change the semantics of a `NULL` `vopts->n`. prototype:

`struct ncplane* ncvisual_blit(struct ncplane* p, const struct ncvisual* ncv, const ncvisual_options *vopts);`

same as `ncvisual_render()`, actually. just the semantics change for `NULL` `vopts->n`; it now gets you a new pile. at the same time, we ought introduce code to `ncvisualplane_create()` to call `ncpile_create()` with a `NULL` n. which means we would need add `notcurses*nc` to its signature, but it's `static inline`, so we can do that freely.
I've made the switch to `ncvisualplane_create()`, and documented it in `NEWS.md`, in `master`. I'm making the other change in `dankamongmen/ncvisual_blit`.
so ought we deprecate `notcurses_render()`?
we're removing the deprecated `notcurses_render_to_file()`, but what about `ncpile_render_to_file()`? are we keeping that?
so ought we deprecate `notcurses_render()`?
we're removing the deprecated `notcurses_render_to_file()`, but what about `ncpile_render_to_file()`? are we keeping that?
so ought we deprecate `notcurses_render()`?
we're removing the deprecated `notcurses_render_to_file()`, but what about `ncpile_render_to_file()`? are we keeping that?
so ought we deprecate `notcurses_render()`?
we're removing the deprecated `notcurses_render_to_file()`, but what about `ncpile_render_to_file()`? are we keeping that?
so ought we deprecate `notcurses_render()`?
we're removing the deprecated `notcurses_render_to_file()`, but what about `ncpile_render_to_file()`? are we keeping that?
so ought we deprecate `notcurses_render()`?
we're removing the deprecated `notcurses_render_to_file()`, but what about `ncpile_render_to_file()`? are we keeping that?
so ought we deprecate `notcurses_render()`?
we're removing the deprecated `notcurses_render_to_file()`, but what about `ncpile_render_to_file()`? are we keeping that?
so ought we deprecate `notcurses_render()`?
we're removing the deprecated `notcurses_render_to_file()`, but what about `ncpile_render_to_file()`? are we keeping that?

Fix patch:
diff --git a/NEWS.md b/NEWS.md
index 22b26e3ae6..9e3b772fb1 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -26,6 +26,17 @@ rearrangements of Notcurses.
   * `ncvisualplane_create()` now allows for a new pile to be created, by
     passing a `NULL` ancestor `ncplane` in `vopts`. The first argument is
     now a `struct notcurses*` rather than a `struct ncplane*`.
+  * `ncvisual_render()` has been deprecated in favor of the new function
+    `ncvisual_blit()`. When a `NULL` `vopts->n` is passed to `ncvisual_blit()`,
+    a new plane is created (as it was in `ncvisual_render()`, but that plane
+    is the root of a new pile, rather than a child of the standard plane.
+    The only tricky conversion is if you previously had `vopts.n` as `NULL`,
+    and were not using `NCVISUAL_OPTION_CHILDPLANE` (or were passing `NULL`
+    as `vopts`). This would result in a new plane bound to the standard plane
+    with `ncvisual_render()`, but with `ncvisual_blit()` it will create a new
+    pile. To keep the behavior, explicitly pass the standard plane as
+    `vopts->n`, and include `NCVISUAL_OPTION_CHILDPLANE` in `vopts->flags`.
+    All other cases will continue to work as they did before.
 
 * 2.4.5 (2021-10-06)
   * The poorly-considered function `ncplane_boundlist()`, added in 2.3.17, has
diff --git a/README.md b/README.md
index 42c8c820f3..771700d53d 100644
--- a/README.md
+++ b/README.md
@@ -295,7 +295,7 @@ If things break or seem otherwise lackluster, **please** consult the
   Notcurses will not make use of bitmap protocols unless the terminal positively
   indicates support for them, even if `NCBLIT_PIXEL` has been requested. Likewise,
   sextants (`NCBLIT_3x2`) won't be used without Unicode 13 support, etc.
-  `ncvisual_render()` will use the best blitter available, unless
+  `ncvisual_blit()` will use the best blitter available, unless
   `NCVISUAL_OPTION_NODEGRADE` is provided (in which case it will fail).
 </details>
 
diff --git a/USAGE.md b/USAGE.md
index 50f95ecb2a..7b96d7b21f 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -3312,8 +3312,8 @@ int ncvisual_set_yx(const struct ncvisual* n, int y, int x, uint32_t pixel);
 // If a subtitle ought be displayed at this time, return a new plane (bound
 // to 'parent' containing the subtitle, which might be text or graphics
 // (depending on the input format).
-struct ncplane* ncvisual_subtitle(struct ncplane* parent,
-                                  const struct ncvisual* ncv);
+struct ncplane* ncvisual_subtitle_plane(struct ncplane* parent,
+                                        const struct ncvisual* ncv);
 ```
 
 And finally, the `ncvisual` can be blitted to one or more `ncplane`s:
@@ -3330,8 +3330,8 @@ And finally, the `ncvisual` can be blitted to one or more `ncplane`s:
 // specify any region beyond the boundaries of the frame. Returns the (possibly
 // newly-created) plane to which we drew. Pixels may not be blitted to the
 // standard plane.
-struct ncplane* ncvisual_render(struct notcurses* nc, struct ncvisual* ncv,
-                                    const struct ncvisual_options* vopts)
+struct ncplane* ncvisual_blit(struct notcurses* nc, struct ncvisual* ncv,
+                              const struct ncvisual_options* vopts);
 
 // Create a new plane as prescribed in opts, either as a child of 'vopts->n',
 // or the root of a new pile if 'vopts->n' is NULL (or 'vopts' itself is NULL).
@@ -3342,7 +3342,7 @@ ncvisualplane_create(struct notcurses* nc, const struct ncplane_options* opts,
                      struct ncvisual* ncv, struct ncvisual_options* vopts);
 
 // decode the next frame ala ncvisual_decode(), but if we have reached the end,
-// rewind to the first frame of the ncvisual. a subsequent `ncvisual_render()`
+// rewind to the first frame of the ncvisual. a subsequent `ncvisual_blit()`
 // will render the first frame, as if the ncvisual had been closed and reopened.
 // the return values remain the same as those of ncvisual_decode().
 int ncvisual_decode_loop(struct ncvisual* nc);
@@ -3435,27 +3435,8 @@ typedef int (*streamcb)(struct ncplane*, struct ncvisual*,
 // Shut up and display my frames! Provide as an argument to ncvisual_stream().
 // If you'd like subtitles to be decoded, provide an ncplane as the curry. If the
 // curry is NULL, subtitles will not be displayed.
-static inline int
-ncvisual_simple_streamer(struct ncplane* n, struct ncvisual* ncv,
-                         const struct timespec* tspec, void* curry){
-  if(notcurses_render(ncplane_notcurses(n))){
-    return -1;
-  }
-  int ret = 0;
-  if(curry){
-    // need a cast for C++ callers
-    struct ncplane* subncp = (struct ncplane*)curry;
-    char* subtitle = ncvisual_subtitle(ncv);
-    if(subtitle){
-      if(ncplane_putstr_yx(subncp, 0, 0, subtitle) < 0){
-        ret = -1;
-      }
-      free(subtitle);
-    }
-  }
-  clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, tspec, NULL);
-  return ret;
-}
+int ncvisual_simple_streamer(struct ncvisual* ncv, struct ncvisual_options* vopts,
+                             const struct timespec* tspec, void* curry);
 
 // Stream the entirety of the media, according to its own timing. Blocking,
 // obviously. streamer may be NULL; it is otherwise called for each frame, and
diff --git a/doc/HACKING.md b/doc/HACKING.md
index 540e431678..1071f34d7f 100644
--- a/doc/HACKING.md
+++ b/doc/HACKING.md
@@ -331,7 +331,7 @@ Its `data` field holds RGBA pixels as provided from disk or memory. Its `pixx`,
 `rowstride` bytes, each containing `pixx` RGBA pixels at the front, plus any
 necessary padding (external libraries might generate padded output).
 
-`ncvisual_render` works with at least four geometries:
+`ncvisual_blit` works with at least four geometries:
 * `vopts->begy`/`begx`: offsets into unscaled data (pixels)
 * `vopts->leny`/`lenx`: lengths of unscaled data to use (pixels)
   * These geometries, when summed, must not exceed `ncv->pixy`/`ncv->pixx`.
diff --git a/doc/man/man3/notcurses_menu.3.md b/doc/man/man3/notcurses_menu.3.md
index 26921e4d11..a78225b00a 100644
--- a/doc/man/man3/notcurses_menu.3.md
+++ b/doc/man/man3/notcurses_menu.3.md
@@ -16,10 +16,10 @@ struct ncplane;
 struct notcurses;
 
 struct ncmenu_section {
-  char* name;  // utf-8 c string
+  const char* name;   // utf-8 c string
   struct ncmenu_item {
-    char* desc;  // utf-8 menu item, NULL for horizontal separator
-    ncinput shortcut;  // shortcut, all should be distinct
+    const char* desc; // utf-8 menu item, NULL for horizontal separator
+    ncinput shortcut; // shortcut, all should be distinct
   }* items;
   int itemcount;
 };
diff --git a/doc/man/man3/notcurses_multiselector.3.md b/doc/man/man3/notcurses_multiselector.3.md
index 2b2df7f0cb..8b8e8fd61d 100644
--- a/doc/man/man3/notcurses_multiselector.3.md
+++ b/doc/man/man3/notcurses_multiselector.3.md
@@ -16,15 +16,14 @@ struct notcurses;
 struct ncmultiselector;
 
 struct ncmselector_item {
-  char* option;
-  char* desc;
-  bool selected;
+  const char* option;
+  const char* desc;
 };
 
 typedef struct ncmultiselector_options {
-  char* title; // title may be NULL, inhibiting riser
-  char* secondary; // secondary may be NULL
-  char* footer; // footer may be NULL
+  const char* title; // title may be NULL, inhibiting riser
+  const char* secondary; // secondary may be NULL
+  const char* footer; // footer may be NULL
   struct ncmselector_item* items; // initial items, statuses
   // default item (selected at start)
   unsigned defidx;
diff --git a/doc/man/man3/notcurses_plane.3.md b/doc/man/man3/notcurses_plane.3.md
index 959f10898a..8cf32811f7 100644
--- a/doc/man/man3/notcurses_plane.3.md
+++ b/doc/man/man3/notcurses_plane.3.md
@@ -439,7 +439,7 @@ the size of a cell in pixels (these ought be the same across planes).
 **maxbmapy** and **maxbmapx** describe the largest bitmap which can be
 displayed in the plane. Any parameter (save **n**) may be **NULL**.
 
-When a plane is blitted to using **ncvisual_render** and **NCBLIT_PIXEL** (see
+When a plane is blitted to using **ncvisual_blit** and **NCBLIT_PIXEL** (see
 **notcurses_visual(3)**), it ceases to accept cell-based output. The sprixel
 will remain associated until a new sprixel is blitted to the plane, the plane
 is resized, the plane is erased, or the plane is destroyed. The base cell of a
diff --git a/doc/man/man3/notcurses_selector.3.md b/doc/man/man3/notcurses_selector.3.md
index 9222d4a98e..e576c1c530 100644
--- a/doc/man/man3/notcurses_selector.3.md
+++ b/doc/man/man3/notcurses_selector.3.md
@@ -16,14 +16,14 @@ struct notcurses;
 struct ncselector;
 
 struct ncselector_item {
-  char* option;
-  char* desc;
+  const char* option;
+  const char* desc;
 };
 
 typedef struct ncselector_options {
-  char* title; // title may be NULL, inhibiting riser
-  char* secondary; // secondary may be NULL
-  char* footer; // footer may be NULL
+  const char* title; // title may be NULL, inhibiting riser
+  const char* secondary; // secondary may be NULL
+  const char* footer; // footer may be NULL
   struct ncselector_item* items; // initial items and descriptions
   // default item (selected at start)
   unsigned defidx;
diff --git a/doc/man/man3/notcurses_tabbed.3.md b/doc/man/man3/notcurses_tabbed.3.md
index e854c42158..bb65a3d532 100644
--- a/doc/man/man3/notcurses_tabbed.3.md
+++ b/doc/man/man3/notcurses_tabbed.3.md
@@ -20,7 +20,7 @@ typedef struct nctabbed_options {
   uint64_t selchan; // channel for the selected tab header
   uint64_t hdrchan; // channel for unselected tab headers
   uint64_t sepchan; // channel for the tab separator
-  char* separator;  // separator string (copied by nctabbed_create())
+  const char* separator; // separator string (copied by nctabbed_create())
   uint64_t flags;   // bitmask of NCTABBED_OPTION_*
 } nctabbed_options;
 
diff --git a/doc/man/man3/notcurses_visual.3.md b/doc/man/man3/notcurses_visual.3.md
index 5f0f441603..718d262f0d 100644
--- a/doc/man/man3/notcurses_visual.3.md
+++ b/doc/man/man3/notcurses_visual.3.md
@@ -75,7 +75,9 @@ typedef int (*streamcb)(struct notcurses*, struct ncvisual*, void*);
 
 **int ncvisual_decode_loop(struct ncvisual* ***ncv***);**
 
-**struct ncplane* ncvisual_render(struct notcurses* ***nc***, struct ncvisual* ***ncv***, const struct ncvisual_options* ***vopts***);**
+**struct ncplane* ncvisual_blit(struct notcurses* ***nc***, struct ncvisual* ***ncv***, const struct ncvisual_options* ***vopts***);**
+
+**static inline struct ncplane* ncvisualplane_create(struct notcurses* ***nc***, const struct ncplane_options* ***opts***, struct ncvisual* ***ncv***, struct ncvisual_options* ***vopts***);**
 
 **static inline struct ncplane* ncvisualplane_create(struct notcurses* ***nc***, const struct ncplane_options* ***opts***, struct ncvisual* ***ncv***, struct ncvisual_options* ***vopts***);**
 
@@ -159,7 +161,7 @@ if the current frame had such a subtitle. Note that the same subtitle might
 be returned for multiple frames, or might not. It is atypical for all frames
 to have subtitles. Subtitles can be text or graphics.
 
-**ncvisual_render** blits the visual to an **ncplane**, based on the contents
+**ncvisual_blit** draws the visual to an **ncplane**, based on the contents
 of its **struct ncvisual_options**. If ***n*** is not **NULL**, it specifies the
 plane on which to render, and ***y***/***x*** specify a location within that plane.
 Otherwise, a new plane will be created, and placed at ***y***/***x*** relative to
@@ -273,7 +275,7 @@ information.
 
 Some terminals support pixel-based output via one of a number of protocols.
 **NCBLIT_PIXEL** has some stringent requirements on the type of planes it can
-be used with; it is usually best to let **ncvisual_render** create the backing
+be used with; it is usually best to let **ncvisual_blit** create the backing
 plane by providing a **NULL** value for **n**. If you must bring your own
 plane, it must be perfectly sized for the bitmap (i.e. large enough, and not
 more than a full cell larger in either dimension--the bitmap, always placed at
@@ -307,12 +309,12 @@ that the entire file is properly-formed.
 failure. It is only necessary for multimedia-based visuals. It advances one
 frame for each call. **ncvisual_decode_loop** has the same return values: when
 called following decoding of the last frame, it will return 1, but a subsequent
-**ncvisual_render** will return the first frame.
+**ncvisual_blit** will return the first frame.
 
 **ncvisual_from_plane** returns **NULL** if the **ncvisual** cannot be created
 and bound. This is usually due to illegal content in the source **ncplane**.
 
-**ncvisual_render** returns **NULL** on error, and otherwise the plane to
+**ncvisual_blit** returns **NULL** on error, and otherwise the plane to
 which the visual was rendered. If **opts->n** is provided, this will be
 **opts->n**. Otherwise, a plane will be created, perfectly sized for the
 visual and the specified blitter.
@@ -369,7 +371,7 @@ sprixel cannot be accessed.
 **ncvisual_rotate** currently supports only **M_PI**/2 and -**M_PI**/2
 radians for **rads**, but this will change soon.
 
-**ncvisual_render** should be able to create new planes in piles other than
+**ncvisual_blit** should be able to create new planes in piles other than
 the standard pile. This ought become a reality soon.
 
 **ncvisual_stream** currently requires a multimedia engine, which is silly.
@@ -378,7 +380,7 @@ This will change in the near future.
 Sprixels interact poorly with multiple planes, and such usage is discouraged.
 This situation might improve in the future.
 
-Multiple threads may not currently call **ncvisual_render** concurrently
+Multiple threads may not currently call **ncvisual_blit** concurrently
 using the same **ncvisual**, even if targeting distinct **ncplane**s. This
 will likely change in the future.
 
diff --git a/include/ncpp/Visual.hh b/include/ncpp/Visual.hh
index 088ebe3e70..e22c8e1390 100644
--- a/include/ncpp/Visual.hh
+++ b/include/ncpp/Visual.hh
@@ -66,9 +66,9 @@ namespace ncpp
 			return ncvisual_decode_loop (visual);
 		}
 
-		ncplane* render (const ncvisual_options* vopts) const NOEXCEPT_MAYBE
+		ncplane* blit (const ncvisual_options* vopts) const NOEXCEPT_MAYBE
 		{
-			return error_guard<ncplane*, ncplane*> (ncvisual_render (get_notcurses (), visual, vopts), nullptr);
+			return error_guard<ncplane*, ncplane*> (ncvisual_blit (get_notcurses (), visual, vopts), nullptr);
 		}
 
 		int stream (const ncvisual_options* vopts, float timescale, ncstreamcb streamer, void *curry = nullptr) const NOEXCEPT_MAYBE
diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h
index 2e0367aee9..5cec6f87e7 100644
--- a/include/notcurses/notcurses.h
+++ b/include/notcurses/notcurses.h
@@ -2899,7 +2899,7 @@ API int ncvisual_decode(struct ncvisual* nc)
   __attribute__ ((nonnull (1)));
 
 // decode the next frame ala ncvisual_decode(), but if we have reached the end,
-// rewind to the first frame of the ncvisual. a subsequent 'ncvisual_render()'
+// rewind to the first frame of the ncvisual. a subsequent 'ncvisual_blit()'
 // will render the first frame, as if the ncvisual had been closed and reopened.
 // the return values remain the same as those of ncvisual_decode().
 API int ncvisual_decode_loop(struct ncvisual* nc)
@@ -2945,6 +2945,21 @@ API int ncvisual_set_yx(const struct ncvisual* n, int y, int x, uint32_t pixel)
 // standard plane.
 API struct ncplane* ncvisual_render(struct notcurses* nc, struct ncvisual* ncv,
                                     const struct ncvisual_options* vopts)
+  __attribute__ ((deprecated)) __attribute__ ((nonnull (2)));
+
+// Render the decoded frame according to the provided options (which may be
+// NULL). The plane used for rendering depends on vopts->n and vopts->flags.
+// If NCVISUAL_OPTION_CHILDPLANE is set, vopts->n must not be NULL, and the
+// plane will always be created as a child of vopts->n. If this flag is not
+// set, and vopts->n is NULL, a new plane is created as root of a new pile.
+// If the flag is not set and vopts->n is not NULL, we render to vopts->n.
+// A subregion of the visual can be rendered using 'begx', 'begy', 'lenx', and
+// 'leny'. Negative values for 'begy' or 'begx' are an error. It is an error to
+// specify any region beyond the boundaries of the frame. Returns the (possibly
+// newly-created) plane to which we drew. Pixels may not be blitted to the
+// standard plane.
+API struct ncplane* ncvisual_blit(struct notcurses* nc, struct ncvisual* ncv,
+                                  const struct ncvisual_options* vopts)
   __attribute__ ((nonnull (2)));
 
 // Create a new plane as prescribed in opts, either as a child of 'vopts->n',
@@ -2972,7 +2987,7 @@ ncvisualplane_create(struct notcurses* nc, const struct ncplane_options* opts,
     memset(vopts, 0, sizeof(*vopts));
   }
   vopts->n = newn;
-  if(ncvisual_render(nc, ncv, vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, vopts) == NULL){
     ncplane_destroy(newn);
     vopts->n = NULL;
     return NULL;
@@ -3047,7 +3062,7 @@ API int ncblit_rgb_loose(const void* data, int linesize,
 
 // The ncpixel API facilitates direct management of the pixels within an
 // ncvisual (ncvisuals keep a backing store of 32-bit RGBA pixels, and render
-// them down to terminal graphics in ncvisual_render()).
+// them down to terminal graphics in ncvisual_blit()).
 //
 // Per libav, we "store as BGRA on little-endian, and ARGB on big-endian".
 // This is an RGBA *byte-order* scheme. libav emits bytes, not words. Those
@@ -3350,17 +3365,15 @@ API void ncplane_greyscale(struct ncplane* n);
 //
 // At all times, exactly one item is selected.
 struct ncselector_item {
-  char* option;
-  char* desc;
-  size_t opcolumns;   // filled in by library
-  size_t desccolumns; // filled in by library
+  const char* option;
+  const char* desc;
 };
 
 typedef struct ncselector_options {
-  char* title; // title may be NULL, inhibiting riser, saving two rows.
-  char* secondary; // secondary may be NULL
-  char* footer; // footer may be NULL
-  struct ncselector_item* items; // initial items and descriptions
+  const char* title; // title may be NULL, inhibiting riser, saving two rows.
+  const char* secondary; // secondary may be NULL
+  const char* footer; // footer may be NULL
+  const struct ncselector_item* items; // initial items and descriptions
   // default item (selected at start), must be < itemcount unless itemcount is
   // 0, in which case 'defidx' must also be 0
   unsigned defidx;
@@ -3409,8 +3422,8 @@ API bool ncselector_offer_input(struct ncselector* n, const ncinput* nc)
 API void ncselector_destroy(struct ncselector* n, char** item);
 
 struct ncmselector_item {
-  char* option;
-  char* desc;
+  const char* option;
+  const char* desc;
   bool selected;
 };
 
@@ -3436,10 +3449,10 @@ struct ncmselector_item {
 // Unlike the selector widget, zero to all of the items can be selected, but
 // also the widget does not support adding or removing items at runtime.
 typedef struct ncmultiselector_options {
-  char* title; // title may be NULL, inhibiting riser, saving two rows.
-  char* secondary; // secondary may be NULL
-  char* footer; // footer may be NULL
-  struct ncmselector_item* items; // initial items, descriptions, and statuses
+  const char* title; // title may be NULL, inhibiting riser, saving two rows.
+  const char* secondary; // secondary may be NULL
+  const char* footer; // footer may be NULL
+  const struct ncmselector_item* items; // initial items, descriptions, and statuses
   // maximum number of options to display at once, 0 to use all available space
   unsigned maxdisplay;
   // exhaustive styling options
@@ -3546,12 +3559,12 @@ API void nctree_destroy(struct nctree* n);
 // a plane resize, menus will be automatically moved/resized. Elements can be
 // dynamically enabled or disabled at all levels (menu, section, and item),
 struct ncmenu_item {
-  char* desc;           // utf-8 menu item, NULL for horizontal separator
+  const char* desc;     // utf-8 menu item, NULL for horizontal separator
   ncinput shortcut;     // shortcut, all should be distinct
 };
 
 struct ncmenu_section {
-  char* name;             // utf-8 c string
+  const char* name;       // utf-8 c string
   int itemcount;
   struct ncmenu_item* items;
   ncinput shortcut;       // shortcut, will be underlined if present in name
@@ -3673,7 +3686,7 @@ typedef struct nctabbed_options {
   uint64_t selchan; // channel for the selected tab header
   uint64_t hdrchan; // channel for unselected tab headers
   uint64_t sepchan; // channel for the tab separator
-  char* separator;  // separator string (copied by nctabbed_create())
+  const char* separator;  // separator string (copied by nctabbed_create())
   uint64_t flags;   // bitmask of NCTABBED_OPTION_*
 } nctabbed_options;
 
@@ -4009,17 +4022,17 @@ typedef struct ncsubproc_options {
 
 // see exec(2). p-types use $PATH. e-type passes environment vars.
 API ALLOC struct ncsubproc* ncsubproc_createv(struct ncplane* n, const ncsubproc_options* opts,
-                                              const char* bin,  char* const arg[],
+                                              const char* bin, char* const arg[],
                                               ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn)
   __attribute__ ((nonnull (1)));
 
 API ALLOC struct ncsubproc* ncsubproc_createvp(struct ncplane* n, const ncsubproc_options* opts,
-                                               const char* bin,  char* const arg[],
+                                               const char* bin, char* const arg[],
                                                ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn)
   __attribute__ ((nonnull (1)));
 
 API ALLOC struct ncsubproc* ncsubproc_createvpe(struct ncplane* n, const ncsubproc_options* opts,
-                                                const char* bin,  char* const arg[], char* const env[],
+                                                const char* bin, char* const arg[], char* const env[],
                                                 ncfdplane_callback cbfxn, ncfdplane_done_cb donecbfxn)
   __attribute__ ((nonnull (1)));
 
diff --git a/src/demo/boxdemo.c b/src/demo/boxdemo.c
index b4bc5a1e7c..242a3c2b78 100644
--- a/src/demo/boxdemo.c
+++ b/src/demo/boxdemo.c
@@ -135,13 +135,14 @@ get_ships(struct notcurses* nc, struct ship* ships, unsigned shipcount){
     return -1;
   }
   struct ncvisual_options vopts = {
+    .n = notcurses_stdplane(nc),
     .y = 30,//rand() % (ncplane_dim_y(notcurses_stdplane_const(nc)) - SHIPHEIGHT),
     .x = 30,//rand() % (ncplane_dim_x(notcurses_stdplane_const(nc)) - SHIPWIDTH),
     .blitter = NCBLIT_PIXEL,
-    .flags = NCVISUAL_OPTION_NODEGRADE,
+    .flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE,
   };
   for(unsigned s = 0 ; s < shipcount ; ++s){
-    if((ships[s].n = ncvisual_render(nc, wmv, &vopts)) == NULL){
+    if((ships[s].n = ncvisual_blit(nc, wmv, &vopts)) == NULL){
       while(s--){
         ncplane_destroy(ships[s].n);
         ncvisual_destroy(wmv);
diff --git a/src/demo/chunli.c b/src/demo/chunli.c
index ec1b000bda..51adb34304 100644
--- a/src/demo/chunli.c
+++ b/src/demo/chunli.c
@@ -23,7 +23,11 @@ chunli_draw(struct notcurses* nc, const char* ext, int count, const nccell* b){
     if(chuns[i].ncv == NULL){
       return -1;
     }
-    if((chuns[i].n = ncvisual_render(nc, chuns[i].ncv, NULL)) == NULL){
+    struct ncvisual_options vopts = {
+      .n = notcurses_stdplane(nc),
+      .flags = NCVISUAL_OPTION_CHILDPLANE,
+    };
+    if((chuns[i].n = ncvisual_blit(nc, chuns[i].ncv, &vopts)) == NULL){
       return -1;
     }
     ncplane_set_base_cell(chuns[i].n, b);
@@ -68,7 +72,11 @@ int chunli_demo(struct notcurses* nc){
     }
     free(path);
     struct ncplane* ncp;
-    if((ncp = ncvisual_render(nc, ncv, NULL)) == NULL){
+    struct ncvisual_options vopts = {
+      .n = notcurses_stdplane(nc),
+      .flags = NCVISUAL_OPTION_CHILDPLANE,
+    };
+    if((ncp = ncvisual_blit(nc, ncv, &vopts)) == NULL){
       return -1;
     }
     ncplane_set_base_cell(ncp, &b);
diff --git a/src/demo/dragon.c b/src/demo/dragon.c
index 18a41b0aa3..bcb96c3e10 100644
--- a/src/demo/dragon.c
+++ b/src/demo/dragon.c
@@ -101,7 +101,7 @@ int dragon_demo(struct notcurses* nc){
       .y = 1,
       .scaling = NCSCALE_STRETCH,
     };
-    if(ncvisual_render(nc, ncv, &vopts) == NULL){
+    if(ncvisual_blit(nc, ncv, &vopts) == NULL){
       ncvisual_destroy(ncv);
       return -1;
     }
diff --git a/src/demo/eagle.c b/src/demo/eagle.c
index 13e5cda8b6..c38ae1d277 100644
--- a/src/demo/eagle.c
+++ b/src/demo/eagle.c
@@ -76,7 +76,7 @@ zoom_map(struct notcurses* nc, const char* map, int* ret){
   vwidth /= xscale;
   vopts.n = zncp;
   vopts.scaling = NCSCALE_STRETCH;
-  if(ncvisual_render(nc, ncv, &vopts) == NULL || (*ret = demo_render(nc))){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL || (*ret = demo_render(nc))){
     ncvisual_destroy(ncv);
     ncplane_destroy(zncp);
     return NULL;
@@ -99,7 +99,7 @@ zoom_map(struct notcurses* nc, const char* map, int* ret){
     if((truex += deltx) > vwidth){
       truex = vwidth;
     }
-    if(ncvisual_render(nc, ncv, &vopts) == NULL){
+    if(ncvisual_blit(nc, ncv, &vopts) == NULL){
       ncvisual_destroy(ncv);
       ncplane_destroy(zncp);
       return NULL;
diff --git a/src/demo/fission.c b/src/demo/fission.c
index db72ed0b9b..63e6dbd9ee 100644
--- a/src/demo/fission.c
+++ b/src/demo/fission.c
@@ -206,8 +206,9 @@ int fission_demo(struct notcurses* nc){
   struct ncvisual_options vopts = {
     .n = stdn,
     .scaling = NCSCALE_STRETCH,
+    .flags = NCVISUAL_OPTION_CHILDPLANE,
   };
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     ncvisual_destroy(ncv);
     goto err;
   }
diff --git a/src/demo/intro.c b/src/demo/intro.c
index 1df3281ecf..4b6462a30c 100644
--- a/src/demo/intro.c
+++ b/src/demo/intro.c
@@ -58,7 +58,7 @@ greatscott(struct notcurses* nc, int dimx){
     .scaling = NCSCALE_STRETCH,
     .flags = NCVISUAL_OPTION_NODEGRADE,
   };
-  struct ncplane* ret = ncvisual_render(nc, ncv, &vopts);
+  struct ncplane* ret = ncvisual_blit(nc, ncv, &vopts);
   ncvisual_destroy(ncv);
   return ret;
 }
@@ -108,7 +108,7 @@ orcashow(struct notcurses* nc, int dimy, int dimx){
     return NULL;
   }
   vopts.n = n;
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     ncplane_destroy(n);
     ncvisual_destroy(ncv);
   }
diff --git a/src/demo/keller.c b/src/demo/keller.c
index 9b7e4364aa..cc5daf3397 100644
--- a/src/demo/keller.c
+++ b/src/demo/keller.c
@@ -23,10 +23,13 @@ visualize(struct notcurses* nc, struct ncvisual* ncv){
   ncplane_set_base(stdn, "", 0, channels);
   for(size_t i = 0 ; i < sizeof(bs) / sizeof(*bs) ; ++i){
     struct ncvisual_options vopts = {
+      .n = notcurses_stdplane(nc),
       .scaling = NCSCALE_SCALE,
       .blitter = bs[i],
-      .flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_HORALIGNED
-                | NCVISUAL_OPTION_VERALIGNED,
+      .flags = NCVISUAL_OPTION_NODEGRADE
+                | NCVISUAL_OPTION_HORALIGNED
+                | NCVISUAL_OPTION_VERALIGNED
+                | NCVISUAL_OPTION_CHILDPLANE,
     };
     int scalex, scaley, truey, truex;
     vopts.x = NCALIGN_CENTER;
@@ -34,7 +37,7 @@ visualize(struct notcurses* nc, struct ncvisual* ncv){
     ncplane_erase(stdn); // to clear out old text
     struct ncplane* n = NULL;
     if(ncvisual_blitter_geom(nc, ncv, &vopts, &truey, &truex, &scaley, &scalex, NULL) == 0){
-      if( (n = ncvisual_render(nc, ncv, &vopts)) ){
+      if( (n = ncvisual_blit(nc, ncv, &vopts)) ){
         ncplane_move_below(n, stdn);
         ncplane_printf_aligned(stdn, ncplane_dim_y(stdn) / 2 - 1, NCALIGN_CENTER,
                               "%03dx%03d", truex, truey);
diff --git a/src/demo/luigi.c b/src/demo/luigi.c
index d8cc5b623e..e1f9d7fa0e 100644
--- a/src/demo/luigi.c
+++ b/src/demo/luigi.c
@@ -158,7 +158,7 @@ int luigi_demo(struct notcurses* nc){
     .scaling = NCSCALE_STRETCH,
     .flags = NCVISUAL_OPTION_NOINTERPOLATE,
   };
-  if(ncvisual_render(nc, nv, &vopts) == NULL){
+  if(ncvisual_blit(nc, nv, &vopts) == NULL){
     return -1;
   }
   assert(1 == ncvisual_decode(nv));
@@ -200,7 +200,11 @@ int luigi_demo(struct notcurses* nc){
   uint64_t channels = 0;
   ncchannels_set_fg_alpha(&channels, NCALPHA_TRANSPARENT);
   ncchannels_set_bg_alpha(&channels, NCALPHA_TRANSPARENT);
-  struct ncplane* wmplane = ncvisual_render(nc, wmncv, NULL);
+  struct ncvisual_options wvopts = {
+    .n = notcurses_stdplane(nc),
+    .flags = NCVISUAL_OPTION_CHILDPLANE,
+  };
+  struct ncplane* wmplane = ncvisual_blit(nc, wmncv, &wvopts);
   if(wmplane == NULL){
     ncvisual_destroy(wmncv);
     return -1;
diff --git a/src/demo/normal.c b/src/demo/normal.c
index f2224adf9b..571c5c818c 100644
--- a/src/demo/normal.c
+++ b/src/demo/normal.c
@@ -84,7 +84,7 @@ rotate_visual(struct notcurses* nc, struct ncplane* n, int dy, int dx){
           .n = stdn,
           .scaling = NCSCALE_STRETCH,
         };
-        if(ncvisual_render(nc, nncv, &nvopts) == NULL){
+        if(ncvisual_blit(nc, nncv, &nvopts) == NULL){
           return -1;
         }
       }
@@ -97,11 +97,14 @@ rotate_visual(struct notcurses* nc, struct ncplane* n, int dy, int dx){
       r = -1;
       break;
     }
+    vopts.n = notcurses_stdplane(nc);
     vopts.x = NCALIGN_CENTER;
     vopts.y = NCALIGN_CENTER;
-    vopts.flags |= NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_VERALIGNED;
+    vopts.flags |= NCVISUAL_OPTION_HORALIGNED
+                   | NCVISUAL_OPTION_VERALIGNED
+                   | NCVISUAL_OPTION_CHILDPLANE;
     struct ncplane* newn;
-    if((newn = ncvisual_render(nc, ncv, &vopts)) == NULL){
+    if((newn = ncvisual_blit(nc, ncv, &vopts)) == NULL){
       r = -1;
       break;
     }
diff --git a/src/demo/outro.c b/src/demo/outro.c
index 2c2124df5c..eba8eabdac 100644
--- a/src/demo/outro.c
+++ b/src/demo/outro.c
@@ -59,9 +59,10 @@ videothread(void* vnc){
     .n = ncp,
     .y = 1,
     .flags = NCVISUAL_OPTION_ADDALPHA,
+    .blitter = NCBLIT_PIXEL,
   };
   int three = 3;
-  if(ncvisual_render(nc, ncv, &ovopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &ovopts) == NULL){
     return NULL;
   }
   struct timespec fade;
@@ -197,8 +198,9 @@ int outro(struct notcurses* nc){
       return -1;
     }
     vopts.scaling = NCSCALE_STRETCH;
-    vopts.flags = NCVISUAL_OPTION_BLEND;
-    if((vopts.n = ncvisual_render(nc, chncv, &vopts)) == NULL){
+    vopts.flags = NCVISUAL_OPTION_BLEND | NCVISUAL_OPTION_CHILDPLANE;
+    vopts.n = notcurses_stdplane(nc);
+    if((vopts.n = ncvisual_blit(nc, chncv, &vopts)) == NULL){
       ncvisual_destroy(chncv);
       return -1;
     }
diff --git a/src/demo/view.c b/src/demo/view.c
index 1debc10fd9..f4bdf5709a 100644
--- a/src/demo/view.c
+++ b/src/demo/view.c
@@ -26,7 +26,7 @@ streamer(struct ncvisual* ncv, struct ncvisual_options* vopts,
       marsh.pipopts.scaling = NCSCALE_STRETCH;
     }
     if(marsh.pipopts.n){
-      ncvisual_render(ncplane_notcurses(marsh.pipopts.n), ncv, &marsh.pipopts);
+      ncvisual_blit(ncplane_notcurses(marsh.pipopts.n), ncv, &marsh.pipopts);
       ncplane_move_above(marsh.pipopts.n, vopts->n);
     }
   }
@@ -130,7 +130,7 @@ view_images(struct notcurses* nc, struct ncplane* nstd, int dimy, int dimx){
     .scaling = NCSCALE_STRETCH,
     .y = 1,
   };
-  if(ncvisual_render(nc, ncv2, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv2, &vopts) == NULL){
     ncvisual_destroy(ncv2);
     ncplane_destroy(dsplane);
     return -1;
@@ -153,7 +153,7 @@ view_images(struct notcurses* nc, struct ncplane* nstd, int dimy, int dimx){
   }
   free(pic);
   vopts.n = notcurses_stdplane(nc);
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     ncvisual_destroy(ncv);
     ncplane_destroy(dsplane);
     return -1;
diff --git a/src/demo/xray.c b/src/demo/xray.c
index 63d07bbc6f..eca6ce6266 100644
--- a/src/demo/xray.c
+++ b/src/demo/xray.c
@@ -101,7 +101,7 @@ get_next_frame(struct ncvisual* ncv, struct ncvisual_options* vopts){
   ret = marsh.next_frame++;
   if(ncvisual_decode(ncv)){
     ret = -1;
-  }else if(ncvisual_render(marsh.nc, ncv, vopts) == NULL){
+  }else if(ncvisual_blit(marsh.nc, ncv, vopts) == NULL){
     ret = -1;
   }
   pthread_mutex_unlock(&lock);
diff --git a/src/demo/yield.c b/src/demo/yield.c
index 8417762166..518cdbd199 100644
--- a/src/demo/yield.c
+++ b/src/demo/yield.c
@@ -39,7 +39,7 @@ int yield_demo(struct notcurses* nc){
     ncplane_destroy(vopts.n);
     return -1;
   }
-  if(ncvisual_render(nc, wmv, &vopts) == NULL){
+  if(ncvisual_blit(nc, wmv, &vopts) == NULL){
     ncvisual_destroy(wmv);
     ncplane_destroy(vopts.n);
     return -1;
@@ -111,7 +111,7 @@ int yield_demo(struct notcurses* nc){
       DEMO_RENDER(nc);
     }while(pfilled == 0);
     tfilled += pfilled;
-    if(ncvisual_render(nc, wmv, &vopts) == NULL){
+    if(ncvisual_blit(nc, wmv, &vopts) == NULL){
       ncvisual_destroy(wmv);
       ncplane_destroy(vopts.n);
       return -1;
diff --git a/src/demo/zoo.c b/src/demo/zoo.c
index 4e3d21d912..3c693ae3ac 100644
--- a/src/demo/zoo.c
+++ b/src/demo/zoo.c
@@ -14,8 +14,9 @@ draw_background(struct notcurses* nc){
     struct ncvisual_options vopts = {
       .scaling = NCSCALE_STRETCH,
       .n = n,
+      .flags = NCVISUAL_OPTION_CHILDPLANE,
     };
-    if(ncvisual_render(nc, ncv, &vopts) == NULL){
+    if(ncvisual_blit(nc, ncv, &vopts) == NULL){
       ncvisual_destroy(ncv);
       return -1;
     }
@@ -27,7 +28,7 @@ draw_background(struct notcurses* nc){
 
 // we list all distributions on which notcurses is known to exist
 static struct ncselector_item select_items[] = {
-#define SITEM(short, long) { short, long, 0, 0, }
+#define SITEM(short, long) { short, long, }
   SITEM("fbsd", "FreeBSD"),
   SITEM("deb", "Debian Unstable Linux"),
   SITEM("rpm", "Fedora Rawhide Linux"),
diff --git a/src/fetch/main.c b/src/fetch/main.c
index 6a112f1a3e..ecf522569c 100644
--- a/src/fetch/main.c
+++ b/src/fetch/main.c
@@ -607,12 +607,13 @@ display_thread(void* vmarshal){
         pixeling = true;
       }
       struct ncvisual_options vopts = {
+        .n = notcurses_stdplane(m->nc),
         .x = NCALIGN_CENTER,
         .blitter = pixeling ? NCBLIT_PIXEL : NCBLIT_3x2,
         .scaling = pixeling ? NCSCALE_NONE : NCSCALE_SCALE_HIRES,
-        .flags = NCVISUAL_OPTION_HORALIGNED,
+        .flags = NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_CHILDPLANE,
       };
-      struct ncplane* iplane = ncvisual_render(m->nc, ncv, &vopts);
+      struct ncplane* iplane = ncvisual_blit(m->nc, ncv, &vopts);
       ncvisual_destroy(ncv);
       if(iplane){
         int x = ncplane_x(iplane);
diff --git a/src/info/main.c b/src/info/main.c
index e4016be4f5..e985ef3a24 100644
--- a/src/info/main.c
+++ b/src/info/main.c
@@ -104,10 +104,31 @@ finish_line(struct ncplane* n){
 
 static int
 emoji_viz(struct ncplane* n){
-  static const char emoji[] = "👾🏴🤘🚬🌍🌎🌏🥆💣🗡🔫⚗️⚛️☢️☣️🌿🎱🏧"
-                              "💉💊"
+  static const char emoji[] = "\U0001f47e" // alien monster
+                              "\U0001f3f4" // waving black flag
+                              "\U0001f918" // sign of the horns
+                              "\U0001f6ac" // cigarette, delicious
+                              "\U0001f30d" // globe europe/africa
+                              "\U0001f30e" // globe americas
+                              "\U0001f30f" // globe asia/australia
+                              "\U0001F946" // rifle
+                              "\U0001f4a3" // bomb
+                              "\U0001f5e1" // dagger
+                              "\U0001F52B" // pistol
+                              "\u2697\ufe0f" // alembic
+                              "\u269b\ufe0f" // atom
+                              "\u2622\ufe0f" // radiation sign
+                              "\u2623\ufe0f" // biohazard
+                              "\U0001F33F" // herb
+                              "\U0001F3B1" // billiards
+                              "\U0001F3E7" // automated teller machine
+                              "\U0001F489" // syringe
+                              "\U0001F48A" // pill
                               "\U0001f574\ufe0f" // man in suit levitating
-                              "📡🤻🦑🇮🇱🇦🇶"
+                              "\U0001F4E1" // satellite antenna
+                              "\U0001F93B" // modern pentathlon
+                              "\U0001F991" // squid
+                              "\U0001f1e6\U0001f1f6" // regional indicators AQ (antarctica)
                               "\U0001f469\u200d\U0001f52c" // woman scientist
                               "\U0001faa4" // mouse trap
                               "\U0001f6b1" // non-potable water
@@ -326,7 +347,7 @@ display_logo(struct ncplane* n, const char* path){
     .blitter = NCBLIT_PIXEL,
     .flags = NCVISUAL_OPTION_CHILDPLANE | NCVISUAL_OPTION_NODEGRADE,
   };
-  struct ncplane* bitm = ncvisual_render(ncplane_notcurses(n), ncv, &vopts);
+  struct ncplane* bitm = ncvisual_blit(ncplane_notcurses(n), ncv, &vopts);
   if(bitm == NULL){
     ncvisual_destroy(ncv);
     return -1;
diff --git a/src/lib/direct.c b/src/lib/direct.c
index ce1a8a6a5e..e8bb14c23b 100644
--- a/src/lib/direct.c
+++ b/src/lib/direct.c
@@ -737,7 +737,7 @@ ncdirect_render_visual(ncdirect* n, ncvisual* ncv,
     }
     ncdv->sprite = bargs.u.pixel.spx;
   }
-  if(ncvisual_blit(ncv, disprows, dispcols, ncdv, bset, &bargs)){
+  if(ncvisual_blit_internal(ncv, disprows, dispcols, ncdv, bset, &bargs)){
     free_plane(ncdv);
     return NULL;
   }
@@ -911,7 +911,7 @@ ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
   }
   int cursor_y = -1;
   int cursor_x = -1;
-  if(interrogate_terminfo(&ret->tcache, termtype, ret->ttyfp, utf8, 1,
+  if(interrogate_terminfo(&ret->tcache, ret->ttyfp, utf8, 1,
                           flags & NCDIRECT_OPTION_INHIBIT_CBREAK,
                           0, &cursor_y, &cursor_x, &ret->stats, 0, 0, 0, 0,
                           flags & NCDIRECT_OPTION_DRAIN_INPUT)){
diff --git a/src/lib/fill.c b/src/lib/fill.c
index 2af185e53a..4679fad17b 100644
--- a/src/lib/fill.c
+++ b/src/lib/fill.c
@@ -641,7 +641,7 @@ int ncplane_qrcode(ncplane* n, int* ymax, int* xmax, const void* data, size_t le
           .n = n,
           .blitter = blitfxn,
         };
-        if(ncvisual_render(ncplane_notcurses(n), ncv, &vopts) == n){
+        if(ncvisual_blit(ncplane_notcurses(n), ncv, &vopts) == n){
           ret = square;
         }
         ncvisual_blitter_geom(ncplane_notcurses(n), ncv, &vopts, NULL, NULL,
diff --git a/src/lib/in.c b/src/lib/in.c
index c316ac1f4d..7605429a35 100644
--- a/src/lib/in.c
+++ b/src/lib/in.c
@@ -23,7 +23,6 @@
 // redirected from a file (NCOPTION_TOSS_INPUT)
 
 // FIXME still need to:
-//  integrate main specials trie with automaton, enable input_errors
 //  probably want pipes/eventfds rather than SIGCONT
 
 static sig_atomic_t resize_seen;
@@ -50,6 +49,12 @@ typedef struct cursorloc {
   int y, x;             // 0-indexed cursor location
 } cursorloc;
 
+#ifndef __MINGW64__
+typedef int ipipe;
+#else
+typedef HANDLE ipipe;
+#endif
+
 // local state for the input thread. don't put this large struct on the stack.
 typedef struct inputctx {
   int stdinfd;          // bulk in fd. always >= 0 (almost always 0). we do not
@@ -100,7 +105,7 @@ typedef struct inputctx {
   unsigned drain;     // drain away bulk input?
   ncsharedstats *stats; // stats shared with notcurses context
 
-  int readypipes[2];  // pipes[0]: poll()able fd indicating the presence of user input
+  ipipe readypipes[2];  // pipes[0]: poll()able fd indicating the presence of user input
   struct initial_responses* initdata;
   struct initial_responses* initdata_complete;
 } inputctx;
@@ -438,10 +443,16 @@ send_synth_signal(int sig){
 }
 
 static void
-mark_pipe_ready(int pipes[static 2]){
+mark_pipe_ready(ipipe pipes[static 2]){
   char sig = 1;
+#ifndef __MINGW64__
   if(write(pipes[1], &sig, sizeof(sig)) != 1){
     logwarn("error writing to readypipe (%d) (%s)\n", pipes[1], strerror(errno));
+#else
+  DWORD wrote;
+  if(!WriteFile(pipes[1], &sig, sizeof(sig), &wrote, NULL) || wrote != sizeof(sig)){
+    logwarn("error writing to readypipe\n");
+#endif
   }
 }
 
@@ -1321,18 +1332,27 @@ build_cflow_automaton(inputctx* ictx){
 }
 
 static void
-endpipes(int pipes[static 2]){
-  if(pipes[0] >= 0){
-    close(pipes[0]);
+closepipe(ipipe p){
+#ifndef __MINGW64__
+  if(p >= 0){
+    close(p);
   }
-  if(pipes[1] >= 0){
-    close(pipes[1]);
+#else
+  if(p){
+    CloseHandle(p);
   }
+#endif
+}
+
+static void
+endpipes(ipipe pipes[static 2]){
+  closepipe(pipes[0]);
+  closepipe(pipes[1]);
 }
 
 // only linux and freebsd13+ have eventfd(), so we'll fall back to pipes sigh.
 static int
-getpipes(int pipes[static 2]){
+getpipes(ipipe pipes[static 2]){
 #ifndef __MINGW64__
 #ifndef __APPLE__
   if(pipe2(pipes, O_CLOEXEC | O_NONBLOCK)){
@@ -1345,19 +1365,21 @@ getpipes(int pipes[static 2]){
     return -1;
   }
   if(set_fd_cloexec(pipes[0], 1, NULL) || set_fd_nonblocking(pipes[0], 1, NULL)){
-    logerror("couldn't prep pipe[0] (%d) (%s)\n", pipes[0], strerror(errno));
+    logerror("couldn't prep pipe[0] (%s)\n", strerror(errno));
     endpipes(pipes);
     return -1;
   }
   if(set_fd_cloexec(pipes[1], 1, NULL) || set_fd_nonblocking(pipes[1], 1, NULL)){
-    logerror("couldn't prep pipe[1] (%d) (%s)\n", pipes[1], strerror(errno));
+    logerror("couldn't prep pipe[1] (%s)\n", strerror(errno));
     endpipes(pipes);
     return -1;
   }
-  // FIXME what to do on windows?
 #endif
 #else // windows
-  pipes[0] = pipes[1] = -1;
+  if(!CreatePipe(&pipes[0], &pipes[1], NULL, BUFSIZ)){
+    logerror("couldn't get pipes\n");
+    return -1;
+  }
 #endif
   return 0;
 }
@@ -1384,6 +1406,7 @@ create_inputctx(tinfo* ti, FILE* infp, int lmargin, int tmargin, int rmargin,
                         if(set_fd_nonblocking(i->stdinfd, 1, &ti->stdio_blocking_save) == 0){
                           i->termfd = tty_check(i->stdinfd) ? -1 : get_tty_fd(infp);
                           memset(i->initdata, 0, sizeof(*i->initdata));
+			  i->initdata->qterm = ti->qterm;
                           i->iread = i->iwrite = i->ivalid = 0;
                           i->cread = i->cwrite = i->cvalid = 0;
                           i->initdata_complete = NULL;
@@ -2026,7 +2049,13 @@ int stop_inputlayer(tinfo* ti){
 }
 
 int inputready_fd(const inputctx* ictx){
+#ifndef __MINGW64__
   return ictx->readypipes[0];
+#else
+  (void)ictx;
+  logerror("readiness descriptor unavailable on windows\n");
+  return -1;
+#endif
 }
 
 static inline uint32_t
@@ -2073,9 +2102,15 @@ internal_get(inputctx* ictx, const struct timespec* ts, ncinput* ni){
   }else if(ictx->ivalid){
     logtrace("draining event readiness pipe\n");
     char c;
+#ifndef __MINGW64__
     while(read(ictx->readypipes[0], &c, sizeof(c)) == 1){
-      // FIXME accelerate;
+      // FIXME accelerate?
     }
+#else
+    while(ReadFile(ictx->readypipes[0], &c, sizeof(c), NULL, NULL)){
+      // FIXME accelerate?
+    }
+#endif
   }
   pthread_mutex_unlock(&ictx->ilock);
   if(sendsignal){
diff --git a/src/lib/internal.h b/src/lib/internal.h
index 68dd46bbc8..720d8781a3 100644
--- a/src/lib/internal.h
+++ b/src/lib/internal.h
@@ -182,28 +182,6 @@ typedef struct ncreel {
   ncreel_options ropts; // copied in ncreel_create()
 } ncreel;
 
-// ncmenu_item and ncmenu_section have internal and (minimal) external forms
-typedef struct ncmenu_int_item {
-  char* desc;           // utf-8 menu item, NULL for horizontal separator
-  ncinput shortcut;     // shortcut, all should be distinct
-  int shortcut_offset;  // column offset with desc of shortcut EGC
-  char* shortdesc;      // description of shortcut, can be NULL
-  int shortdesccols;    // columns occupied by shortcut description
-  bool disabled;        // disabled?
-} ncmenu_int_item;
-
-typedef struct ncmenu_int_section {
-  char* name;             // utf-8 c string
-  int itemcount;
-  ncmenu_int_item* items; // items, NULL iff itemcount == 0
-  ncinput shortcut;       // shortcut, will be underlined if present in name
-  int xoff;               // column offset from beginning of menu bar
-  int bodycols;           // column width of longest item
-  int itemselected;       // current item selected, -1 for no selection
-  int shortcut_offset;    // column offset within name of shortcut EGC
-  int enabled_item_count; // number of enabled items: section is disabled iff 0
-} ncmenu_int_section;
-
 typedef struct ncfdplane {
   ncfdplane_callback cb;      // invoked with fresh hot data
   ncfdplane_done_cb donecb;   // invoked on EOF (if !follow) or error
@@ -235,19 +213,6 @@ typedef struct ncreader {
   bool manage_cursor;         // enable and place a virtual cursor
 } ncreader;
 
-typedef struct ncmenu {
-  ncplane* ncp;
-  int sectioncount;         // must be positive
-  ncmenu_int_section* sections; // NULL iff sectioncount == 0
-  int unrolledsection;      // currently unrolled section, -1 if none
-  int headerwidth;          // minimum space necessary to display all sections
-  uint64_t headerchannels;  // styling for header
-  uint64_t dissectchannels; // styling for disabled section headers
-  uint64_t sectionchannels; // styling for sections
-  uint64_t disablechannels; // styling for disabled entries
-  bool bottom;              // are we on the bottom (vs top)?
-} ncmenu;
-
 typedef struct ncprogbar {
   ncplane* ncp;
   double progress;          // on the range [0, 1]
@@ -265,18 +230,6 @@ typedef struct nctab {
   struct nctab* next;
 } nctab;
 
-typedef struct nctabbed {
-  ncplane* ncp;          // widget ncplane
-  ncplane* p;            // tab content ncplane
-  ncplane* hp;           // tab headers ncplane
-  // a doubly-linked circular list of tabs
-  nctab* leftmost;       // the tab most to the left
-  nctab* selected;       // the currently selected tab
-  int tabcount;          // tab separator (can be NULL)
-  int sepcols;           // separator with in columns
-  nctabbed_options opts; // copied in nctabbed_create()
-} nctabbed;
-
 // various moving parts within a notcurses context (and the user) might need to
 // access the stats object, so throw a lock on it. we don't want the lock in
 // the actual structure since (a) it's usually unnecessary and (b) it breaks
@@ -1040,7 +993,7 @@ calc_gradient_channels(uint64_t* channels, uint64_t ul, uint64_t ur,
 }
 
 // ncdirect needs to "fake" an isolated ncplane as a drawing surface for
-// ncvisual_render(), and thus calls these low-level internal functions.
+// ncvisual_blit(), and thus calls these low-level internal functions.
 // they are not for general use -- check ncplane_new() and ncplane_destroy().
 ncplane* ncplane_new_internal(notcurses* nc, ncplane* n, const ncplane_options* nopts);
 
@@ -1051,9 +1004,9 @@ ALLOC char* ncplane_vprintf_prep(const char* format, va_list ap);
 
 // Resize the provided ncvisual to the specified 'rows' x 'cols', but do not
 // change the internals of the ncvisual. Uses oframe.
-int ncvisual_blit(struct ncvisual* ncv, int rows, int cols,
-                  ncplane* n, const struct blitset* bset,
-                  const blitterargs* bargs);
+int ncvisual_blit_internal(struct ncvisual* ncv, int rows, int cols,
+                           ncplane* n, const struct blitset* bset,
+                           const blitterargs* bargs);
 
 static inline int
 tty_emit(const char* seq, int fd){
diff --git a/src/lib/menu.c b/src/lib/menu.c
index 3c981f76d4..d2663d9da4 100644
--- a/src/lib/menu.c
+++ b/src/lib/menu.c
@@ -1,5 +1,40 @@
 #include "internal.h"
 
+// ncmenu_item and ncmenu_section have internal and (minimal) external forms
+typedef struct ncmenu_int_item {
+  char* desc;           // utf-8 menu item, NULL for horizontal separator
+  ncinput shortcut;     // shortcut, all should be distinct
+  int shortcut_offset;  // column offset with desc of shortcut EGC
+  char* shortdesc;      // description of shortcut, can be NULL
+  int shortdesccols;    // columns occupied by shortcut description
+  bool disabled;        // disabled?
+} ncmenu_int_item;
+
+typedef struct ncmenu_int_section {
+  char* name;             // utf-8 c string
+  int itemcount;
+  ncmenu_int_item* items; // items, NULL iff itemcount == 0
+  ncinput shortcut;       // shortcut, will be underlined if present in name
+  int xoff;               // column offset from beginning of menu bar
+  int bodycols;           // column width of longest item
+  int itemselected;       // current item selected, -1 for no selection
+  int shortcut_offset;    // column offset within name of shortcut EGC
+  int enabled_item_count; // number of enabled items: section is disabled iff 0
+} ncmenu_int_section;
+
+typedef struct ncmenu {
+  ncplane* ncp;
+  int sectioncount;         // must be positive
+  ncmenu_int_section* sections; // NULL iff sectioncount == 0
+  int unrolledsection;      // currently unrolled section, -1 if none
+  int headerwidth;          // minimum space necessary to display all sections
+  uint64_t headerchannels;  // styling for header
+  uint64_t dissectchannels; // styling for disabled section headers
+  uint64_t sectionchannels; // styling for sections
+  uint64_t disablechannels; // styling for disabled entries
+  bool bottom;              // are we on the bottom (vs top)?
+} ncmenu;
+
 // Search the provided multibyte (UTF8) string 's' for the provided unicode
 // codepoint 'cp'. If found, return the column offset of the EGC in which the
 // codepoint appears in 'col', and the byte offset as the return value. If not
diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c
index 578f03b41f..ae6c4a3301 100644
--- a/src/lib/notcurses.c
+++ b/src/lib/notcurses.c
@@ -1065,7 +1065,7 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
                   &ret->rstate.logendy : &fakecursory;
   int* cursorx = opts->flags & NCOPTION_PRESERVE_CURSOR ?
                   &ret->rstate.logendx : &fakecursorx;
-  if(interrogate_terminfo(&ret->tcache, opts->termtype, ret->ttyfp, utf8,
+  if(interrogate_terminfo(&ret->tcache, ret->ttyfp, utf8,
                           opts->flags & NCOPTION_NO_ALTERNATE_SCREEN, 0,
                           opts->flags & NCOPTION_NO_FONT_CHANGES,
                           cursory, cursorx, &ret->stats,
diff --git a/src/lib/plot.c b/src/lib/plot.c
index f52981fcf1..9d993a0d60 100644
--- a/src/lib/plot.c
+++ b/src/lib/plot.c
@@ -220,7 +220,7 @@ int redraw_pixelplot_##T(nc##X##plot* ncp){ \
     .blitter = NCBLIT_PIXEL, \
     .flags = NCVISUAL_OPTION_NODEGRADE, \
   }; \
-  if(ncvisual_render(ncplane_notcurses(ncp->plot.ncp), ncv, &vopts) == NULL){ \
+  if(ncvisual_blit(ncplane_notcurses(ncp->plot.ncp), ncv, &vopts) == NULL){ \
     ncvisual_destroy(ncv); \
     return -1; \
   } \
diff --git a/src/lib/selector.c b/src/lib/selector.c
index 97132a0cca..d3992f8941 100644
--- a/src/lib/selector.c
+++ b/src/lib/selector.c
@@ -1,5 +1,19 @@
 #include "internal.h"
 
+// internal ncselector item
+struct ncselector_int {
+  char* option;
+  char* desc;
+  size_t opcolumns;   // filled in by library
+  size_t desccolumns; // filled in by library
+};
+
+struct ncmselector_int {
+  char* option;
+  char* desc;
+  bool selected;
+};
+
 typedef struct ncselector {
   ncplane* ncp;                  // backing ncplane
   unsigned selected;             // index of selection
@@ -7,7 +21,7 @@ typedef struct ncselector {
   unsigned maxdisplay;           // max number of items to display, 0 -> no limit
   int longop;                    // columns occupied by longest option
   int longdesc;                  // columns occupied by longest description
-  struct ncselector_item* items; // list of items and descriptions, heap-copied
+  struct ncselector_int* items;  // list of items and descriptions, heap-copied
   unsigned itemcount;            // number of pairs in 'items'
   char* title;                   // can be NULL, in which case there's no riser
   int titlecols;                 // columns occupied by title
@@ -29,7 +43,7 @@ typedef struct ncmultiselector {
   unsigned startdisp;             // index of first option displayed
   unsigned maxdisplay;            // max number of items to display, 0 -> no limit
   int longitem;                   // columns occupied by longest item
-  struct ncmselector_item* items; // items, descriptions, and statuses, heap-copied
+  struct ncmselector_int* items;  // items, descriptions, and statuses, heap-copied
   unsigned itemcount;             // number of pairs in 'items'
   char* title;                    // can be NULL, in which case there's no riser
   int titlecols;                  // columns occupied by title
@@ -332,7 +346,7 @@ int ncselector_additem(ncselector* n, const struct ncselector_item* item){
   int origdimy, origdimx;
   ncselector_dim_yx(n, &origdimy, &origdimx);
   size_t newsize = sizeof(*n->items) * (n->itemcount + 1);
-  struct ncselector_item* items = realloc(n->items, newsize);
+  struct ncselector_int* items = realloc(n->items, newsize);
   if(!items){
     return -1;
   }
diff --git a/src/lib/tabbed.c b/src/lib/tabbed.c
index 52f728f02d..8dcd5886c4 100644
--- a/src/lib/tabbed.c
+++ b/src/lib/tabbed.c
@@ -1,5 +1,25 @@
 #include "internal.h"
 
+typedef struct nctabbed_opsint {
+  uint64_t selchan; // channel for the selected tab header
+  uint64_t hdrchan; // channel for unselected tab headers
+  uint64_t sepchan; // channel for the tab separator
+  char* separator;  // separator string (copied by nctabbed_create())
+  uint64_t flags;   // bitmask of NCTABBED_OPTION_*
+} nctabbed_opsint;
+
+typedef struct nctabbed {
+  ncplane* ncp;          // widget ncplane
+  ncplane* p;            // tab content ncplane
+  ncplane* hp;           // tab headers ncplane
+  // a doubly-linked circular list of tabs
+  nctab* leftmost;       // the tab most to the left
+  nctab* selected;       // the currently selected tab
+  int tabcount;          // tab separator (can be NULL)
+  int sepcols;           // separator with in columns
+  nctabbed_opsint opts;  // copied in nctabbed_create()
+} nctabbed;
+
 void nctabbed_redraw(nctabbed* nt){
   nctab* t;
   int drawn_cols = 0;
@@ -144,8 +164,12 @@ nctabbed* nctabbed_create(ncplane* n, const nctabbed_options* topts){
   nt->leftmost = nt->selected = NULL;
   nt->tabcount = 0;
   memcpy(&nt->opts, topts, sizeof(*topts));
-  if(nt->opts.separator){
-    if((nt->opts.separator = strdup(nt->opts.separator)) == NULL){
+  nt->opts.selchan = topts->selchan;
+  nt->opts.hdrchan = topts->hdrchan;
+  nt->opts.sepchan = topts->sepchan;
+  nt->opts.flags = topts->flags;
+  if(topts->separator){
+    if((nt->opts.separator = strdup(topts->separator)) == NULL){
       logerror("Couldn't allocate nctabbed separator");
       free(nt);
       return NULL;
@@ -157,6 +181,7 @@ nctabbed* nctabbed_create(ncplane* n, const nctabbed_options* topts){
       return NULL;
     }
   }else{
+    nt->opts.separator = NULL;
     nt->sepcols = 0;
   }
   ncplane_dim_yx(n, &nrows, &ncols);
@@ -267,6 +292,7 @@ int nctabbed_del(nctabbed* nt, nctab* t){
     t->next->prev = t->prev;
     t->prev->next = t->next;
   }
+  free(t->name);
   free(t);
   --nt->tabcount;
   return 0;
diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c
index 63deba55b5..1ac94bca98 100644
--- a/src/lib/termdesc.c
+++ b/src/lib/termdesc.c
@@ -609,7 +609,9 @@ apply_term_heuristics(tinfo* ti, const char* termname, queried_terminals_e qterm
     // FIXME what, oh what to do with tmux?
   }else if(qterm == TERMINAL_GNUSCREEN){
     termname = "GNU screen";
-    // FIXME disable rgb?
+    if(compare_versions(ti->termversion, "5.0") < 0){
+      ti->caps.rgb = false;
+    }
   }else if(qterm == TERMINAL_MLTERM){
     termname = "MLterm";
     ti->caps.quadrants = true; // good caps.quadrants, no caps.sextants as of 3.9.0
@@ -644,11 +646,12 @@ apply_term_heuristics(tinfo* ti, const char* termname, queried_terminals_e qterm
     }
     ti->bce = true;
   }else if(qterm == TERMINAL_MSTERMINAL){
-    ti->termname = "Windows Terminal";
+    termname = "Windows Terminal";
     ti->caps.rgb = true;
   }else if(qterm == TERMINAL_CONTOUR){
     termname = "Contour";
     ti->caps.quadrants = true;
+    ti->caps.sextants = true;
     ti->caps.rgb = true;
     *invertsixel = true;
   }else if(qterm == TERMINAL_ITERM){
@@ -766,11 +769,12 @@ macos_early_matches(void){
 // Device Attributes, allowing us to get a negative response if our queries
 // aren't supported by the terminal. we fire it off early because we have a
 // full round trip before getting the reply, which is likely to pace init.
-int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out, unsigned utf8,
+int interrogate_terminfo(tinfo* ti, FILE* out, unsigned utf8,
                          unsigned noaltscreen, unsigned nocbreak, unsigned nonewfonts,
                          int* cursor_y, int* cursor_x, ncsharedstats* stats,
                          int lmargin, int tmargin, int rmargin, int bmargin,
                          unsigned draininput){
+  const char* termtype = getenv("TERM");
   int foolcursor_x, foolcursor_y;
   if(!cursor_x){
     cursor_x = &foolcursor_x;
diff --git a/src/lib/termdesc.h b/src/lib/termdesc.h
index ac63c7df4f..2f36bf51cc 100644
--- a/src/lib/termdesc.h
+++ b/src/lib/termdesc.h
@@ -222,15 +222,13 @@ term_supported_styles(const tinfo* ti){
 // prepare |ti| from the terminfo database and other sources. set |utf8| if
 // we've verified UTF8 output encoding. set |noaltscreen| to inhibit alternate
 // screen detection. |stats| may be NULL; either way, it will be handed to the
-// input layer so that its stats can be recorded. if |termtype| is not NULL, it
-// will be used to look up the terminfo database entry; the value of TERM is
-// otherwise used.
-int interrogate_terminfo(tinfo* ti, const char* termtype, FILE* out,
-                         unsigned utf8, unsigned noaltscreen, unsigned nocbreak,
+// input layer so that its stats can be recorded.
+int interrogate_terminfo(tinfo* ti, FILE* out, unsigned utf8,
+                         unsigned noaltscreen, unsigned nocbreak,
                          unsigned nonewfonts, int* cursor_y, int* cursor_x,
                          struct ncsharedstats* stats, int lmargin, int tmargin,
                          int rmargin, int bmargin, unsigned draininput)
-  __attribute__ ((nonnull (1, 3, 10)));
+  __attribute__ ((nonnull (1, 2, 9)));
 
 void free_terminfo_cache(tinfo* ti);
 
diff --git a/src/lib/visual.c b/src/lib/visual.c
index 3557b4ad35..0ad20b3c86 100644
--- a/src/lib/visual.c
+++ b/src/lib/visual.c
@@ -70,8 +70,8 @@ ncplane* ncvisual_subtitle_plane(ncplane* parent, const ncvisual* ncv){
   return visual_implementation.visual_subtitle(parent, ncv);
 }
 
-int ncvisual_blit(ncvisual* ncv, int rows, int cols, ncplane* n,
-                  const struct blitset* bset, const blitterargs* barg){
+int ncvisual_blit_internal(ncvisual* ncv, int rows, int cols, ncplane* n,
+                           const struct blitset* bset, const blitterargs* barg){
   if(!(barg->flags & NCVISUAL_OPTION_NOINTERPOLATE)){
     if(visual_implementation.visual_blit){
       if(visual_implementation.visual_blit(ncv, rows, cols, n, bset, barg) < 0){
@@ -804,14 +804,15 @@ ncplane* ncvisual_render_cells(notcurses* nc, ncvisual* ncv, const struct blitse
   ncplane* createdn = NULL;
 //fprintf(stderr, "INPUT N: %p\n", n);
   if(n == NULL || (flags & NCVISUAL_OPTION_CHILDPLANE)){ // create plane
-    if(n == NULL){
-      n = notcurses_stdplane(nc);
-    }
     if(scaling == NCSCALE_NONE || scaling == NCSCALE_NONE_HIRES){
       dispcols = lenx;
       disprows = leny;
     }else{
-      ncplane_dim_yx(n, &disprows, &dispcols);
+      if(n == NULL){
+        ncplane_dim_yx(notcurses_stdplane(nc), &disprows, &dispcols);
+      }else{
+        ncplane_dim_yx(n, &disprows, &dispcols);
+      }
       dispcols *= encoding_x_scale(&nc->tcache, bset);
       disprows *= encoding_y_scale(&nc->tcache, bset);
       if(scaling == NCSCALE_SCALE || scaling == NCSCALE_SCALE_HIRES){
@@ -837,7 +838,12 @@ ncplane* ncvisual_render_cells(notcurses* nc, ncvisual* ncv, const struct blitse
     if(flags & NCVISUAL_OPTION_VERALIGNED){
       nopts.flags |= NCPLANE_OPTION_VERALIGNED;
     }
-    if((n = ncplane_create(n, &nopts)) == NULL){
+    if(n){
+      n = ncplane_create(n, &nopts);
+    }else{
+      n = ncpile_create(nc, &nopts);
+    }
+    if(n == NULL){
       return NULL;
     }
     createdn = n;
@@ -882,7 +888,7 @@ ncplane* ncvisual_render_cells(notcurses* nc, ncvisual* ncv, const struct blitse
   bargs.flags = flags;
   bargs.u.cell.placey = placey;
   bargs.u.cell.placex = placex;
-  if(ncvisual_blit(ncv, disprows, dispcols, n, bset, &bargs)){
+  if(ncvisual_blit_internal(ncv, disprows, dispcols, n, bset, &bargs)){
     ncplane_destroy(createdn);
     return NULL;
   }
@@ -904,7 +910,11 @@ make_sprixel_plane(notcurses* nc, ncplane* parent, ncvisual* ncv,
                    uint64_t flags, int* outy, int* outx,
                    int* placey, int* placex, int pxoffy, int pxoffx){
   if(scaling != NCSCALE_NONE && scaling != NCSCALE_NONE_HIRES){
-    ncplane_dim_yx(parent, disppixy, disppixx);
+    if(parent == NULL){
+      ncplane_dim_yx(notcurses_stdplane(nc), disppixy, disppixx);
+    }else{
+      ncplane_dim_yx(parent, disppixy, disppixx);
+    }
     // FIXME why do we clamp only vertical, not horizontal, here?
     if(*placey + *disppixy >= ncplane_dim_y(notcurses_stdplane_const(nc))){
       *disppixy = ncplane_dim_y(notcurses_stdplane_const(nc)) - *placey;
@@ -943,8 +953,12 @@ make_sprixel_plane(notcurses* nc, ncplane* parent, ncvisual* ncv,
            | ((flags & NCVISUAL_OPTION_VERALIGNED) ? NCPLANE_OPTION_VERALIGNED : 0),
   };
 //fprintf(stderr, "PLACING NEW PLANE: %d/%d @ %d/%d 0x%016lx\n", nopts.rows, nopts.cols, nopts.y, nopts.x, nopts.flags);
-  // FIXME might need some particular pile
-  ncplane* n = ncplane_create(parent, &nopts);
+  ncplane* n;
+  if(parent == NULL){
+    n = ncpile_create(nc, &nopts);
+  }else{
+    n = ncplane_create(parent, &nopts);
+  }
   if(n == NULL){
     return NULL;
   }
@@ -988,9 +1002,6 @@ ncplane* ncvisual_render_pixels(notcurses* nc, ncvisual* ncv, const struct blits
   int disppixy = 0, disppixx = 0, outy = 0, outx = 0;
   ncplane* createdn = NULL;
   if(n == NULL || (flags & NCVISUAL_OPTION_CHILDPLANE)){ // create plane
-    if(n == NULL){
-      n = notcurses_stdplane(nc);
-    }
     if((createdn = make_sprixel_plane(nc, n, ncv, scaling, &disppixy, &disppixx,
                                       flags, &outy, &outx, &placey, &placex,
                                       pxoffy, pxoffx)) == NULL){
@@ -1075,7 +1086,7 @@ ncplane* ncvisual_render_pixels(notcurses* nc, ncvisual* ncv, const struct blits
   // at this point, disppixy/disppixx are the output geometry (might be larger
   // than scaledy/scaledx for sixel), while scaledy/scaledx are the scaled
   // geometry. cells occupied are determined based off disppixy/disppixx.
-  if(ncvisual_blit(ncv, disppixy, disppixx, n, bset, &bargs)){
+  if(ncvisual_blit_internal(ncv, disppixy, disppixx, n, bset, &bargs)){
     ncplane_destroy(createdn);
     return NULL;
   }
@@ -1118,7 +1129,7 @@ ncplane* ncvisual_render_pixels(notcurses* nc, ncvisual* ncv, const struct blits
   return n;
 }
 
-ncplane* ncvisual_render(notcurses* nc, ncvisual* ncv, const struct ncvisual_options* vopts){
+ncplane* ncvisual_blit(notcurses* nc, ncvisual* ncv, const struct ncvisual_options* vopts){
   const struct blitset* bset;
   int leny, lenx;
   if(ncvisual_blitset_geom(nc, &nc->tcache, ncv, vopts, NULL, NULL, NULL, NULL,
@@ -1152,6 +1163,23 @@ ncplane* ncvisual_render(notcurses* nc, ncvisual* ncv, const struct ncvisual_opt
   return n;
 }
 
+// compatability wrapper around ncvisual_blit that provides the standard plane
+// plus NCVISUAL_OPTION_CHILDPLANE if vopts->n is NULL.
+ncplane* ncvisual_render(notcurses* nc, ncvisual* ncv, const struct ncvisual_options* vopts){
+  struct ncvisual_options fakevopts;
+  if(vopts == NULL){
+    memset(&fakevopts, 0, sizeof(fakevopts));
+  }else{
+    memcpy(&fakevopts, vopts, sizeof(fakevopts));
+  }
+  vopts = &fakevopts;
+  if(vopts->n == NULL){
+    fakevopts.n = notcurses_stdplane(nc);
+    fakevopts.flags |= NCVISUAL_OPTION_CHILDPLANE;
+  }
+  return ncvisual_blit(nc, ncv, vopts);
+}
+
 ncvisual* ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int begx,
                               int leny, int lenx){
   int py, px;
diff --git a/src/lib/windows.c b/src/lib/windows.c
index ba1c4bda77..09e0704132 100644
--- a/src/lib/windows.c
+++ b/src/lib/windows.c
@@ -88,6 +88,7 @@ int prepare_windows_terminal(tinfo* ti, size_t* tablelen, size_t* tableused){
     return -1;
   }
   loginfo("verified Windows ConPTY\n");
+  ti->qterm = TERMINAL_MSTERMINAL;
   return 0;
 }
 #endif
diff --git a/src/media/ffmpeg.c b/src/media/ffmpeg.c
index fe521a8ab9..dba857949b 100644
--- a/src/media/ffmpeg.c
+++ b/src/media/ffmpeg.c
@@ -200,7 +200,7 @@ struct ncplane* ffmpeg_subtitle(ncplane* parent, const ncvisual* ncv){
         .blitter = NCBLIT_PIXEL,
         .scaling = NCSCALE_STRETCH,
       };
-      if(ncvisual_render(nc, v, &vopts) == NULL){
+      if(ncvisual_blit(nc, v, &vopts) == NULL){
         ncplane_destroy(vn);
         ncvisual_destroy(v);
         return NULL;
@@ -501,7 +501,7 @@ int ffmpeg_stream(notcurses* nc, ncvisual* ncv, float timescale,
     // was actually rendered with
     ncvisual_blitter_geom(nc, ncv, &activevopts, NULL, NULL, NULL, NULL,
                           &activevopts.blitter);
-    if((newn = ncvisual_render(nc, ncv, &activevopts)) == NULL){
+    if((newn = ncvisual_blit(nc, ncv, &activevopts)) == NULL){
       if(activevopts.n != vopts->n){
         ncplane_destroy(activevopts.n);
       }
diff --git a/src/media/oiio-indep.c b/src/media/oiio-indep.c
index d7d92fd895..61a2569ea9 100644
--- a/src/media/oiio-indep.c
+++ b/src/media/oiio-indep.c
@@ -28,7 +28,7 @@ int oiio_stream(struct notcurses* nc, ncvisual* ncv, float timescale,
     // decay the blitter explicitly, so that the callback knows the blitter it
     // was actually rendered with
     ncvisual_blitter_geom(nc, ncv, &activevopts, NULL, NULL, NULL, NULL, &activevopts.blitter);
-    if((newn = ncvisual_render(nc, ncv, &activevopts)) == NULL){
+    if((newn = ncvisual_blit(nc, ncv, &activevopts)) == NULL){
       if(activevopts.n != vopts->n){
         ncplane_destroy(activevopts.n);
       }
diff --git a/src/player/play.cpp b/src/player/play.cpp
index d8e7cee957..3a38bf730b 100644
--- a/src/player/play.cpp
+++ b/src/player/play.cpp
@@ -47,7 +47,7 @@ struct marshal {
 auto perframe(struct ncvisual* ncv, struct ncvisual_options* vopts,
               const struct timespec* abstime, void* vmarshal) -> int {
   struct marshal* marsh = static_cast<struct marshal*>(vmarshal);
-  NotCurses &nc = NotCurses::get_instance ();
+  NotCurses &nc = NotCurses::get_instance();
   auto start = static_cast<struct timespec*>(ncplane_userptr(vopts->n));
   if(!start){
     start = static_cast<struct timespec*>(malloc(sizeof(struct timespec)));
@@ -112,17 +112,18 @@ auto perframe(struct ncvisual* ncv, struct ncvisual_options* vopts,
       continue;
     }
     if(keyp == ' '){
-      if((keyp = nc.get(true)) == (uint32_t)-1){
-        ncplane_destroy(subp);
-        return -1;
-      }
+      do{
+        if((keyp = nc.get(true, &ni)) == (uint32_t)-1){
+          ncplane_destroy(subp);
+          return -1;
+        }
+      }while(ni.id != 'q' && (ni.evtype == EvType::Release || ni.id != ' '));
     }
     // if we just hit a non-space character to unpause, ignore it
-    if(keyp == ' '){ // space for unpause
-      continue;
-    }
     if(keyp == NCKey::Resize){
       return 0;
+    }else if(keyp == ' '){ // space for unpause
+      continue;
     }else if(keyp == 'L' && ni.ctrl && !ni.alt){
       nc.refresh(nullptr, nullptr);
       continue;
@@ -144,6 +145,8 @@ auto perframe(struct ncvisual* ncv, struct ncvisual_options* vopts,
     }else if(keyp == NCKey::Left){
       // FIXME move backwards
       continue;
+    }else if(keyp != 'q'){
+      continue;
     }
     ncplane_destroy(subp);
     return 1;
diff --git a/src/poc/bitmapstates.c b/src/poc/bitmapstates.c
index 8c63bdd2e5..3544acc2e3 100644
--- a/src/poc/bitmapstates.c
+++ b/src/poc/bitmapstates.c
@@ -27,8 +27,10 @@ wipebitmap(struct notcurses* nc){
   }
   struct ncvisual_options vopts = {
     .blitter = NCBLIT_PIXEL,
+    .n = notcurses_stdplane(nc),
+    .flags = NCVISUAL_OPTION_CHILDPLANE,
   };
-  struct ncplane* n = ncvisual_render(nc, ncv, &vopts);
+  struct ncplane* n = ncvisual_blit(nc, ncv, &vopts);
   if(n == NULL){
     return -1;
   }
@@ -84,7 +86,7 @@ wipebitmap(struct notcurses* nc){
   if(ncve == NULL){
     return -1;
   }
-  if((n = ncvisual_render(nc, ncve, &vopts)) == NULL){
+  if((n = ncvisual_blit(nc, ncve, &vopts)) == NULL){
     return -1;
   }
   emit(notcurses_stdplane(nc), "Ought see empty square");
@@ -96,7 +98,7 @@ wipebitmap(struct notcurses* nc){
 
   // now, actually wipe the middle with *s, and then ensure that a new render
   // gets wiped out before being displayed
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     return -1;
   }
   emit(notcurses_stdplane(nc), "Ought see full square");
@@ -114,7 +116,7 @@ wipebitmap(struct notcurses* nc){
   notcurses_render(nc);
   sleep(2);
 
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     return -1;
   }
   emit(notcurses_stdplane(nc), "Ought *still* see 16 *s");
diff --git a/src/poc/blitters.c b/src/poc/blitters.c
index d8b7080f7f..ca383c46d3 100644
--- a/src/poc/blitters.c
+++ b/src/poc/blitters.c
@@ -45,9 +45,11 @@ int main(int argc, char** argv){
         struct ncvisual_options vopts = {
           .scaling = scaling,
           .blitter = *blitter,
+          .n = notcurses_stdplane(nc),
+          .flags = NCVISUAL_OPTION_CHILDPLANE,
         };
         struct ncplane* cn;
-        if((cn = ncvisual_render(nc, ncv, &vopts)) == NULL){
+        if((cn = ncvisual_blit(nc, ncv, &vopts)) == NULL){
           ncvisual_destroy(ncv);
           goto err;
         }
diff --git a/src/poc/grid.c b/src/poc/grid.c
index c4bc63aa77..728f74b135 100644
--- a/src/poc/grid.c
+++ b/src/poc/grid.c
@@ -67,10 +67,11 @@ int main(void){
     return EXIT_FAILURE;
   }
   struct ncvisual_options vopts = {
+    .n = stdn,
     .blitter = NCBLIT_PIXEL,
-    .flags = NCVISUAL_OPTION_NODEGRADE,
+    .flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE,
   };
-  struct ncplane* bitn = ncvisual_render(nc, ncv, &vopts);
+  struct ncplane* bitn = ncvisual_blit(nc, ncv, &vopts);
   ncvisual_destroy(ncv);
   if(bitn == NULL){
     notcurses_stop(nc);
diff --git a/src/poc/interp.c b/src/poc/interp.c
index e435df3c44..59f665a828 100644
--- a/src/poc/interp.c
+++ b/src/poc/interp.c
@@ -19,10 +19,12 @@ interp(struct notcurses* nc, int cellpixy, int cellpixx){
     return -1;
   }
   struct ncvisual_options vopts = {
+    .n = stdn,
     .y = 1,
     .blitter = NCBLIT_PIXEL,
+    .flags = NCVISUAL_OPTION_CHILDPLANE | NCVISUAL_OPTION_NODEGRADE,
   };
-  struct ncplane* ncvp = ncvisual_render(nc, ncv, &vopts);
+  struct ncplane* ncvp = ncvisual_blit(nc, ncv, &vopts);
   if(ncvp == NULL){
     free(randrgb);
     return -1;
@@ -38,7 +40,7 @@ interp(struct notcurses* nc, int cellpixy, int cellpixx){
   vopts.n = scalep;
   vopts.scaling = NCSCALE_STRETCH;
   popts.x += ncplane_dim_x(scalep) + 1;
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     free(randrgb);
     return -1;
   }
@@ -46,7 +48,7 @@ interp(struct notcurses* nc, int cellpixy, int cellpixx){
   struct ncplane* scalepni = ncplane_create(stdn, &popts);
   vopts.n = scalepni;
   vopts.flags = NCVISUAL_OPTION_NOINTERPOLATE;
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     free(randrgb);
     return -1;
   }
@@ -64,7 +66,7 @@ interp(struct notcurses* nc, int cellpixy, int cellpixx){
   vopts.flags = 0;
   vopts.n = resizep;
   vopts.scaling = NCSCALE_NONE;
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     free(randrgb);
     return -1;
   }
@@ -81,11 +83,12 @@ interp(struct notcurses* nc, int cellpixy, int cellpixx){
   if(ncvisual_resize_noninterpolative(ncv, popts.rows * cellpixy, popts.cols * cellpixx)){
     return -1;
   }
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     return -1;
   }
   ncplane_putstr_yx(stdn, 2, 41, "resize(no)");
   notcurses_render(nc);
+  ncvisual_destroy(ncv);
   ncplane_destroy(ncvp);
   ncplane_destroy(scalep);
   ncplane_destroy(scalepni);
@@ -107,7 +110,9 @@ int main(void){
     goto err;
   }
   ncinput ni;
-  notcurses_getc_blocking(nc, &ni);
+  do{
+    notcurses_getc_blocking(nc, &ni);
+  }while(ni.id != (uint32_t)-1 && ni.evtype != NCTYPE_RELEASE);
   notcurses_stop(nc);
   return EXIT_SUCCESS;
 
diff --git a/src/poc/menu.c b/src/poc/menu.c
index 134d0e1ee7..97bc58c480 100644
--- a/src/poc/menu.c
+++ b/src/poc/menu.c
@@ -30,12 +30,12 @@ run_menu(struct notcurses* nc, struct ncmenu* ncm){
   notcurses_render(nc);
   while((keypress = notcurses_getc_blocking(nc, &ni)) != (uint32_t)-1){
     if(!ncmenu_offer_input(ncm, &ni)){
-      if(keypress == 'q'){
+      if(ni.evtype == NCTYPE_RELEASE){
+        continue;
+      }else if(keypress == 'q'){
         ncmenu_destroy(ncm);
         ncplane_destroy(selplane);
         return 0;
-      }else if(ni.evtype == NCTYPE_RELEASE){
-        continue;
       }else if(keypress == NCKEY_ENTER){ // selected a menu item
         const char* sel;
         if( (sel = ncmenu_selected(ncm, &ni)) ){
diff --git a/src/poc/multiselect.c b/src/poc/multiselect.c
index cec03ea31c..b976ad966b 100644
--- a/src/poc/multiselect.c
+++ b/src/poc/multiselect.c
@@ -100,7 +100,7 @@ int main(void){
       .scaling = NCSCALE_STRETCH,
       .n = n,
     };
-    if(ncvisual_render(nc, ncv, &vopts) == NULL){
+    if(ncvisual_blit(nc, ncv, &vopts) == NULL){
       goto err;
     }
   }
diff --git a/src/poc/pixel.c b/src/poc/pixel.c
index ea2eca7546..7a66e869ce 100644
--- a/src/poc/pixel.c
+++ b/src/poc/pixel.c
@@ -15,12 +15,14 @@ handle(struct notcurses* nc, const char *fn){
       uint64_t channels = NCCHANNELS_INITIALIZER(rand() % 256, rand() % 256, 100, rand() % 256, 100, 140);
       ncplane_set_base(stdn, "a", 0, channels);
       struct ncvisual_options vopts = {
+        .n = notcurses_stdplane(nc),
         .y = y,
         .x = x,
         .scaling = NCSCALE_NONE_HIRES,
         .blitter = NCBLIT_PIXEL,
+        .flags = NCVISUAL_OPTION_CHILDPLANE | NCVISUAL_OPTION_NODEGRADE,
       };
-      struct ncplane* nv = ncvisual_render(nc, ncv, &vopts);
+      struct ncplane* nv = ncvisual_blit(nc, ncv, &vopts);
       if(nv == NULL){
         ncvisual_destroy(ncv);
         return -1;
diff --git a/src/poc/rotate.c b/src/poc/rotate.c
index ea1369c063..3cb89c020a 100644
--- a/src/poc/rotate.c
+++ b/src/poc/rotate.c
@@ -43,7 +43,7 @@ int main(int argc, char** argv){
   }
   int scaley, scalex;
   vopts.n = n;
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if((ncvisual_blit(nc, ncv, &vopts)) == NULL){
     goto err;
   }
   if(notcurses_render(nc)){
@@ -56,8 +56,7 @@ int main(int argc, char** argv){
   if(ncvisual_resize(ncv, dimy * scaley, dimx * scalex)){
     goto err;
   }
-  vopts.n = n;
-  if(ncvisual_render(nc, ncv, &vopts) == NULL){
+  if(ncvisual_blit(nc, ncv, &vopts) == NULL){
     goto err;
   }
   if(notcurses_render(nc)){
@@ -65,11 +64,12 @@ int main(int argc, char** argv){
   }
   clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
 
-  vopts.n = NULL;
   vopts.x = NCALIGN_CENTER;
   vopts.y = NCALIGN_CENTER;
-  vopts.flags |= NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_VERALIGNED;
-  ncplane_destroy(n);
+  vopts.flags |= NCVISUAL_OPTION_HORALIGNED
+                 | NCVISUAL_OPTION_VERALIGNED
+                 | NCVISUAL_OPTION_CHILDPLANE;
+  vopts.n = notcurses_stdplane(nc);
   for(double i = 0 ; i < 256 ; ++i){
     clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
     if(ncvisual_rotate(ncv, M_PI / ((i / 32) + 2))){
@@ -77,7 +77,7 @@ int main(int argc, char** argv){
       break;
     }
     struct ncplane* newn;
-    if((newn = ncvisual_render(nc, ncv, &vopts)) == NULL){
+    if((newn = ncvisual_blit(nc, ncv, &vopts)) == NULL){
       failed = true;
       break;
     }
diff --git a/src/poc/selector.c b/src/poc/selector.c
index 1a8d1b0d13..ff329db5bd 100644
--- a/src/poc/selector.c
+++ b/src/poc/selector.c
@@ -6,7 +6,7 @@
 #include "version.h"
 
 static struct ncselector_item items[] = {
-#define SITEM(short, long) { short, long, 0, 0, }
+#define SITEM(s, l) { s, l, }
   SITEM("Afrikaans", "Ek kan glas eet, dit maak my nie seer nie."),
   SITEM("AngloSax", "ᛁᚳ᛫ᛗᚨᚷ᛫ᚷᛚᚨᛋ᛫ᛖᚩᛏᚪᚾ᛫ᚩᚾᛞ᛫ᚻᛁᛏ᛫ᚾᛖ᛫ᚻᛖᚪᚱᛗᛁᚪᚧ᛫ᛗᛖ᛬"),
   SITEM("Japanese", "私はガラスを食べられます。それは私を傷つけません。"),
@@ -90,7 +90,7 @@ int main(void){
       .scaling = NCSCALE_STRETCH,
       .n = n,
     };
-    if(ncvisual_render(nc, ncv, &vopts) == NULL){
+    if(ncvisual_blit(nc, ncv, &vopts) == NULL){
       goto err;
     }
     ncvisual_destroy(ncv);
diff --git a/src/poc/statepixel.c b/src/poc/statepixel.c
index 4487926a03..9d71efdf8f 100644
--- a/src/poc/statepixel.c
+++ b/src/poc/statepixel.c
@@ -41,10 +41,11 @@ handle(struct notcurses* nc, const char* fn){
   }
   // render by itself
   struct ncvisual_options vopts = {
+    .n = notcurses_stdplane(nc),
     .blitter = NCBLIT_PIXEL,
-    .flags = NCVISUAL_OPTION_NODEGRADE,
+    .flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE,
   };
-  struct ncplane* n = ncvisual_render(nc, ncv, &vopts);
+  struct ncplane* n = ncvisual_blit(nc, ncv, &vopts);
   if(n == NULL){
     ncvisual_destroy(ncv);
     return -1;
diff --git a/src/poc/tabbed.c b/src/poc/tabbed.c
index 3741f3ede2..76f2b4fe4c 100644
--- a/src/poc/tabbed.c
+++ b/src/poc/tabbed.c
@@ -102,7 +102,11 @@ int main(int argc, char** argv){
   }
   int tabnameind = 0;
   uint32_t c;
-  while((c = notcurses_getc_blocking(nc, NULL)) != 'q'){
+  ncinput ni;
+  while((c = notcurses_getc_blocking(nc, &ni)) != 'q'){
+    if(ni.evtype == NCTYPE_RELEASE){
+      continue;
+    }
     switch(c){
       case NCKEY_RIGHT:
         nctabbed_next(nct);
diff --git a/src/poc/textplay.c b/src/poc/textplay.c
deleted file mode 100644
index db91d4a2b0..0000000000
--- a/src/poc/textplay.c
+++ /dev/null
@@ -1,231 +0,0 @@
-#include <stdlib.h>
-#include <notcurses/notcurses.h>
-#include "compat/compat.c"
-
-#define GIG 1000000000ull
-static const uint32_t LOWCOLOR = 0x004080;
-static const uint32_t HICOLOR = 0xddffdd;
-static const uint32_t NANOSEC = GIG / 60; // 60 cps
-static const int FRAMERATIO = 2; // display 1 of every FRAMERATIO frames
-#define MARGIN 2
-
-static struct notcurses*
-init(void){
-  struct notcurses_options opts = {
-    //.loglevel = NCLOGLEVEL_DEBUG,
-    .margin_t = MARGIN / 2,
-    .margin_r = MARGIN,
-    .margin_b = MARGIN / 2,
-    .margin_l = MARGIN,
-    .flags = NCOPTION_NO_ALTERNATE_SCREEN |
-             NCOPTION_SUPPRESS_BANNERS,
-  };
-  struct notcurses* nc = notcurses_init(&opts, stdout);
-  return nc;
-}
-
-// normally called with hicolor of HICOLOR, but sometimes we need to dull
-// it out (when we pause, and at the end)
-static int
-colorize(struct ncplane* n, uint32_t hicolor){
-  int y, x;
-  ncplane_cursor_yx(n, &y, &x);
-  uint32_t c = hicolor;
-  ncplane_set_bg_rgb(n, 0x222222);
-  while(y >= 0){
-    while(x >= 0){
-      uint16_t stylemask;
-      uint64_t channels;
-      char* egc = ncplane_at_yx(n, y, x, &stylemask, &channels);
-      if(egc == NULL){
-        return -1;
-      }
-      if(strcmp(egc, "") == 0){
-        free(egc);
-        --x;
-        continue;
-      }
-      ncplane_set_fg_rgb(n, c);
-      if(ncplane_putegc_yx(n, y, x, egc, NULL) < 0){
-        free(egc);
-        return -1;
-      }
-      if(c != LOWCOLOR){
-        unsigned g = (c & 0xff00) >> 8u;
-        --g;
-        // only the leading character gets the bright almost-white
-        c = (c & 0x000080) | (g << 8u);
-      }
-      free(egc);
-      --x;
-    }
-    --y;
-    x = ncplane_dim_x(n) - 1;
-  }
-  return 0;
-}
-
-// whenever the text indicates a lengthy delay, keep the frame rolling...
-static int
-longdelay(struct notcurses* nc, struct ncvisual* ncv, struct ncvisual_options* vopts){
-  struct timespec now;
-  clock_gettime(CLOCK_MONOTONIC, &now);
-  uint64_t nowns, deadlinens;
-  nowns = now.tv_sec * GIG + now.tv_nsec;
-  deadlinens = nowns + 5ull * GIG;
-  do{
-    if(ncv){
-      for(int i = 0 ; i < FRAMERATIO ; ++i){
-        ncvisual_decode(ncv);
-      }
-      if(ncvisual_render(nc, ncv, vopts) == NULL){
-        return -1;
-      }
-      if(notcurses_render(nc)){
-        return -1;
-      }
-    }else{
-      // FIXME clock_nanosleep
-    }
-    clock_gettime(CLOCK_MONOTONIC, &now);
-    nowns = now.tv_sec * GIG + now.tv_nsec;
-  }while(nowns < deadlinens);
-  return 0;
-}
-
-static int
-textplay(struct notcurses* nc, struct ncplane* tplane, struct ncvisual* ncv){
-  char* buf = NULL;
-  size_t buflen = 1;
-  struct ncplane* stdn = notcurses_stdplane(nc);
-  ncplane_set_scrolling(tplane, true);
-  struct ncvisual_options vopts = {
-    .n = ncplane_dup(stdn, NULL),
-    .scaling = NCSCALE_STRETCH,
-    .blitter = NCBLIT_PIXEL,
-  };
-  ncplane_move_below(vopts.n, tplane);
-  wint_t wc;
-  while((wc = getwc(stdin)) != WEOF){
-    if(ncv){
-      if(ncvisual_render(nc, ncv, &vopts) == NULL){
-        return -1;
-      }
-    }
-    ncplane_erase(tplane);
-    char conv[7];
-    mbstate_t mbs = {};
-    size_t w = wcrtomb(conv, wc, &mbs);
-    if(w == (size_t)-1){
-      goto err;
-    }
-    conv[w] = '\0';
-    char* tmp = realloc(buf, buflen + w);
-    if(tmp == NULL){
-      goto err;
-    }
-    buf = tmp;
-    strcpy(buf + buflen - 1, conv);
-    buflen += w;
-    int pt = ncplane_puttext(tplane, 0, NCALIGN_LEFT, buf, NULL);
-    if(pt < 0){
-      goto err;
-    }
-    if(colorize(tplane, HICOLOR)){
-      goto err;
-    }
-    if(notcurses_render(nc)){
-      goto err;
-    }
-    struct timespec ts = {
-      .tv_sec = 0, .tv_nsec = NANOSEC,
-    };
-    if(!ncv){
-      clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
-    }else{
-      for(int i = 0 ; i < FRAMERATIO ; ++i){
-        ncvisual_decode(ncv);
-      }
-    }
-    if(wc == L'…'){
-      if(notcurses_render(nc)){
-        goto err;
-      }
-      if(longdelay(nc, ncv, &vopts)){
-        goto err;
-      }
-    }
-  }
-  // dull out the leading character for exit
-  if(colorize(tplane, 0x00ff80)){
-    goto err;
-  }
-  if(notcurses_render(nc)){
-    goto err;
-  }
-  ncplane_destroy(vopts.n);
-  return 0;
-
-err:
-  ncplane_destroy(vopts.n);
-  free(buf);
-  return -1;
-}
-
-static struct ncplane*
-textplane(struct notcurses* nc){
-  int dimy, dimx;
-  struct ncplane* stdn = notcurses_stddim_yx(nc, &dimy, &dimx);
-  struct ncplane_options nopts = {
-    .y = MARGIN * 2 + 2,
-    .x = MARGIN * 4 + 2,
-    .rows = dimy - MARGIN * 4 + 1,
-    .cols = dimx - MARGIN * 8,
-    .name = "text",
-  };
-  struct ncplane* n = ncplane_create(stdn, &nopts);
-  uint64_t channels = NCCHANNELS_INITIALIZER(0, 0, 0, 0x22, 0x22, 0x22);
-  /*ncchannels_set_fg_alpha(&channels, NCALPHA_TRANSPARENT);
-  ncchannels_set_bg_alpha(&channels, NCALPHA_TRANSPARENT);*/
-  if(n){
-    ncplane_set_base(n, "", 0, channels);
-  }
-  return n;
-}
-
-static void
-usage(FILE* fp){
-  fprintf(fp, "usage: textplay [ media ]\n");
-}
-
-int main(int argc, char** argv){
-  const char* media = NULL;
-  if(argc > 3){
-    usage(stderr);
-    return EXIT_FAILURE;
-  }else if(argc == 2){
-    media = argv[1];
-  }
-  struct notcurses* nc = init();
-  if(nc == NULL){
-    return EXIT_FAILURE;
-  }
-  struct ncvisual* ncv = NULL;
-  if(media){
-    if((ncv = ncvisual_from_file(media)) == NULL){
-      notcurses_stop(nc);
-      return EXIT_FAILURE;
-    }
-  }
-  struct ncplane* tplane = textplane(nc);
-  if(tplane == NULL){
-    notcurses_stop(nc);
-    return EXIT_FAILURE;
-  }
-  textplay(nc, tplane, ncv);
-  ncvisual_destroy(ncv);
-  if(notcurses_stop(nc)){
-    return EXIT_FAILURE;
-  }
-  return EXIT_SUCCESS;
-}
diff --git a/src/pocpp/resize.cpp b/src/pocpp/resize.cpp
index bae398f828..4ee8520ae9 100644
--- a/src/pocpp/resize.cpp
+++ b/src/pocpp/resize.cpp
@@ -30,9 +30,11 @@ int main(int argc, char** argv){
   if(!ncv){
     goto err;
   }
+  vopts.n = notcurses_stdplane(nc);
   vopts.scaling = NCSCALE_STRETCH;
+  vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
   struct ncplane* ntarg;
-  if((ntarg = ncvisual_render(nc, ncv, &vopts)) == nullptr){
+  if((ntarg = ncvisual_blit(nc, ncv, &vopts)) == nullptr){
     goto err;
   }
   if(notcurses_render(nc)){
diff --git a/src/tetris/background.h b/src/tetris/background.h
index 7bea2df01d..ec85179bf7 100644
--- a/src/tetris/background.h
+++ b/src/tetris/background.h
@@ -3,7 +3,9 @@ void DrawBackground(const std::string& s) { // drawn to the standard plane
   backg_->decode();
   ncvisual_options opts{};
   opts.scaling = NCSCALE_STRETCH;
-  backg_->render(&opts);
+  opts.n = *nc_.get_stdplane();
+  opts.flags = NCVISUAL_OPTION_CHILDPLANE;
+  backg_->blit(&opts);
 }
 
 void DrawBoard() { // draw all fixed components of the game


Test patch:
diff --git a/src/tests/bitmap.cpp b/src/tests/bitmap.cpp
index 26c7f83b79..95a873ca11 100644
--- a/src/tests/bitmap.cpp
+++ b/src/tests/bitmap.cpp
@@ -28,10 +28,11 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
     CHECK(0 == ncvisual_resize(ncv, 1, 1));
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto s = n->sprite;
     REQUIRE(nullptr != s);
@@ -54,7 +55,7 @@ TEST_CASE("Bitmaps") {
     int maxy, maxx;
     ncplane_pixel_geom(n_, nullptr, nullptr, nullptr, nullptr, &maxy, &maxx);
     CHECK(0 == ncvisual_resize(ncv, maxy, maxx));
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nn == n);
     auto s = n->sprite;
     REQUIRE(nullptr != s);
@@ -81,7 +82,7 @@ TEST_CASE("Bitmaps") {
     vopts.n = n;
     vopts.blitter = NCBLIT_PIXEL;
     vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    CHECK(nullptr == ncvisual_render(nc_, ncv, &vopts));
+    CHECK(nullptr == ncvisual_blit(nc_, ncv, &vopts));
     CHECK(0 == notcurses_render(nc_));
     ncvisual_destroy(ncv);
     CHECK(0 == ncplane_destroy(n));
@@ -104,7 +105,7 @@ TEST_CASE("Bitmaps") {
     vopts.n = n;
     vopts.blitter = NCBLIT_PIXEL;
     vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    CHECK(nullptr == ncvisual_render(nc_, ncv, &vopts));
+    CHECK(nullptr == ncvisual_blit(nc_, ncv, &vopts));
     CHECK(0 == notcurses_render(nc_));
     ncvisual_destroy(ncv);
     CHECK(0 == ncplane_destroy(n));
@@ -126,9 +127,10 @@ TEST_CASE("Bitmaps") {
     };
     auto n = ncplane_create(n_, &nopts);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    CHECK(nullptr != ncvisual_render(nc_, ncv, &vopts));
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    CHECK(nullptr != ncvisual_blit(nc_, ncv, &vopts));
     CHECK(0 > ncplane_putchar_yx(n, ' ', 0, 0));
     CHECK(0 == notcurses_render(nc_));
     ncvisual_destroy(ncv);
@@ -143,9 +145,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto botn = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto botn = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != botn);
     // should just have a red plane
     CHECK(0 == notcurses_render(nc_));
@@ -154,7 +157,7 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v2(x * y, htole(0x8142f1ff));
     auto ncv2 = ncvisual_from_rgba(v2.data(), y, sizeof(decltype(v2)::value_type) * x, x);
     REQUIRE(nullptr != ncv2);
-    auto topn = ncvisual_render(nc_, ncv2, &vopts);
+    auto topn = ncvisual_blit(nc_, ncv2, &vopts);
     REQUIRE(nullptr != topn);
     // should have a yellow plane partially obscuring a red one
     CHECK(0 == notcurses_render(nc_));
@@ -179,9 +182,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_file(find_data("worldmap.png").get());
     REQUIRE(ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto newn = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto newn = ncvisual_blit(nc_, ncv, &vopts);
     CHECK(newn);
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncplane_destroy(newn));
@@ -199,9 +203,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto s = n->sprite;
     REQUIRE(nullptr != s);
@@ -220,18 +225,20 @@ TEST_CASE("Bitmaps") {
     auto bigp = ncplane_create(n_, &nopts);
     REQUIRE(bigp);
     vopts.n = bigp;
+    vopts.flags &= ~NCVISUAL_OPTION_CHILDPLANE;
     uint64_t white = NCCHANNELS_INITIALIZER(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
     ncplane_set_base(bigp, "x", 0, white);
-    CHECK(vopts.n == ncvisual_render(nc_, ncv, &vopts));
+    CHECK(vopts.n == ncvisual_blit(nc_, ncv, &vopts));
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncvisual_resize_noninterpolative(ncv, ncv->pixy * 4, ncv->pixx * 4));
     CHECK(4 * nc_->tcache.cellpixy == ncv->pixy);
     CHECK(4 * nc_->tcache.cellpixx == ncv->pixx);
     vopts.y = 1;
     vopts.x = 6;
-    vopts.n = nullptr;
+    vopts.n = n_;
     vopts.scaling = NCSCALE_NONE;
-    auto infn = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags |= NCVISUAL_OPTION_CHILDPLANE;
+    auto infn = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(infn);
     if(nc_->tcache.sprixel_scale_height == 6){
       if(4 * nc_->tcache.cellpixy % 6){
@@ -249,7 +256,7 @@ TEST_CASE("Bitmaps") {
     CHECK(ncv->pixy == 8);
     CHECK(ncv->pixx == 8);
     vopts.x = 11;
-    auto resizen = ncvisual_render(nc_, ncv, &vopts);
+    auto resizen = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(resizen);
     CHECK((8 + nc_->tcache.cellpixy - 1) / nc_->tcache.cellpixy == ncplane_dim_y(resizen));
     CHECK((8 + nc_->tcache.cellpixx - 1) / nc_->tcache.cellpixx == ncplane_dim_x(resizen));
@@ -272,14 +279,15 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     struct ncplane_options nopts = {
       .y = 2,
       .x = 0,
-      .rows = 5,
+      .rows = 4,
       .cols = 4,
       .userptr = nullptr, .name = "scale", .resizecb = nullptr,
       .flags = 0, .margin_b = 0, .margin_r = 0,
@@ -288,14 +296,15 @@ TEST_CASE("Bitmaps") {
     REQUIRE(nullptr != nres);
     vopts.n = nres;
     vopts.scaling = NCSCALE_SCALE;
-    ncvisual_render(nc_, ncv, &vopts);
+    ncvisual_blit(nc_, ncv, &vopts);
     CHECK(4 == ncplane_dim_x(vopts.n));
     CHECK(0 == ncvisual_resize_noninterpolative(ncv, ncv->pixy * 4, ncv->pixx * 4));
-    vopts.n = nullptr;
+    vopts.n = nres;
     vopts.y = 2;
     vopts.x = 5;
     vopts.scaling = NCSCALE_NONE;
-    auto ninf = ncvisual_render(nc_, ncv, &vopts);
+    auto ninf = ncvisual_blit(nc_, ncv, &vopts);
+notcurses_debug(nc_, stderr);
     REQUIRE(nullptr != ninf);
     CHECK(ncplane_dim_y(nres) == ncplane_dim_y(ninf));
     CHECK(ncplane_dim_x(nres) == ncplane_dim_x(ninf));
@@ -339,7 +348,7 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y, htole(0xffffffff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    auto child = ncvisual_render(nc_, ncv, &vopts);
+    auto child = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(child);
     CHECK(18 == ncplane_dim_y(child));
     CHECK(5 == ncplane_dim_x(child));
@@ -382,7 +391,7 @@ TEST_CASE("Bitmaps") {
     std::vector<uint32_t> v(x * y, htole(0xffffffff));
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
-    auto child = ncvisual_render(nc_, ncv, &vopts);
+    auto child = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(child);
     CHECK(6 == ncplane_dim_y(child));
     CHECK(1 == ncplane_dim_x(child));
@@ -401,9 +410,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto s = n->sprite;
     REQUIRE(nullptr != s);
@@ -432,9 +442,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto s = n->sprite;
     REQUIRE(nullptr != s);
@@ -463,9 +474,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto s = n->sprite;
     REQUIRE(nullptr != s);
@@ -502,9 +514,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     ncvisual_destroy(ncv);
     CHECK(0 == notcurses_render(nc_));
@@ -561,10 +574,11 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_file(find_data("worldmap.png").get());
     REQUIRE(ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
     vopts.scaling = NCSCALE_STRETCH;
-    auto newn = ncvisual_render(nc_, ncv, &vopts);
+    auto newn = ncvisual_blit(nc_, ncv, &vopts);
     CHECK(newn);
     ncplane_move_bottom(newn);
     CHECK(0 == notcurses_render(nc_));
@@ -592,9 +606,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     for(int i = 0 ; i <= ncplane_dim_x(n_) ; ++i){
       CHECK(0 == ncplane_move_yx(n, 0, i));
@@ -612,10 +627,11 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
     vopts.x = ncplane_dim_x(n_) - 3;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     for(int i = ncplane_dim_x(n_) - 3 ; i >= 0 ; --i){
       CHECK(0 == ncplane_move_yx(n, 0, i));
@@ -633,9 +649,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     for(int i = 0 ; i <= ncplane_dim_y(n_) ; ++i){
       CHECK(0 == ncplane_move_yx(n, i, 0));
@@ -653,10 +670,11 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
     vopts.y = ncplane_dim_y(n_) - 3;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     for(int i = ncplane_dim_y(n_) - 3 ; i >= 0 ; --i){
       CHECK(0 == ncplane_move_yx(n, i, 0));
@@ -675,9 +693,10 @@ TEST_CASE("Bitmaps") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     auto xpx = (ncplane_dim_x(n_) - 2) * nc_->tcache.cellpixx;
     auto ypx = (ncplane_dim_y(n_) - 2) * nc_->tcache.cellpixy;
@@ -689,7 +708,7 @@ TEST_CASE("Bitmaps") {
       vopts.y = yat / nc_->tcache.cellpixy;
       vopts.pxoffy = yat % nc_->tcache.cellpixy;
       CHECK(0 == ncplane_destroy(n));
-      n = ncvisual_render(nc_, ncv, &vopts);
+      n = ncvisual_blit(nc_, ncv, &vopts);
       REQUIRE(nullptr != n);
       CHECK(0 == notcurses_render(nc_));
     }
diff --git a/src/tests/blit.cpp b/src/tests/blit.cpp
index 5d4f9aa801..ed4ce428b8 100644
--- a/src/tests/blit.cpp
+++ b/src/tests/blit.cpp
@@ -123,7 +123,9 @@ TEST_CASE("Blit") {
         REQUIRE(nullptr != ncv);
         struct ncvisual_options vopts{};
         vopts.blitter = NCBLIT_2x2;
-        auto ncp = ncvisual_render(nc_, ncv, &vopts);
+        vopts.n = n_;
+        vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
+        auto ncp = ncvisual_blit(nc_, ncv, &vopts);
         ncvisual_destroy(ncv);
         REQUIRE(nullptr != ncp);
         CHECK(0 == notcurses_render(nc_));
@@ -149,7 +151,9 @@ TEST_CASE("Blit") {
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts{};
     vopts.blitter = NCBLIT_1x1;
-    auto p = ncvisual_render(nc_, ncv, &vopts);
+    vopts.n = n_;
+    vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
+    auto p = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != p);
     CHECK(0 == notcurses_render(nc_));
     CHECK(1 == ncplane_dim_y(p));
@@ -175,7 +179,9 @@ TEST_CASE("Blit") {
       REQUIRE(nullptr != ncv);
       struct ncvisual_options vopts{};
       vopts.blitter = NCBLIT_2x1;
-      auto p = ncvisual_render(nc_, ncv, &vopts);
+      vopts.n = n_;
+      vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
+      auto p = ncvisual_blit(nc_, ncv, &vopts);
       REQUIRE(nullptr != p);
       CHECK(1 == ncplane_dim_y(p));
       CHECK(4 == ncplane_dim_x(p));
@@ -218,7 +224,9 @@ TEST_CASE("Blit") {
       REQUIRE(nullptr != ncv);
       struct ncvisual_options vopts{};
       vopts.blitter = NCBLIT_2x2;
-      auto p = ncvisual_render(nc_, ncv, &vopts);
+      vopts.n = n_;
+      vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
+      auto p = ncvisual_blit(nc_, ncv, &vopts);
       REQUIRE(nullptr != p);
       CHECK(1 == ncplane_dim_y(p));
       CHECK(16 == ncplane_dim_x(p));
diff --git a/src/tests/media.cpp b/src/tests/media.cpp
index a31424c87f..fce2f4c1ef 100644
--- a/src/tests/media.cpp
+++ b/src/tests/media.cpp
@@ -28,21 +28,23 @@ TEST_CASE("Media") {
     struct ncvisual_options vopts{};
     vopts.x = NCALIGN_CENTER;
     vopts.y = NCALIGN_CENTER;
-    vopts.flags |= NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_VERALIGNED;
-    auto p = ncvisual_render(nc_, ncv, &vopts);
+    vopts.n = n_;
+    vopts.flags |= NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_VERALIGNED
+                    | NCVISUAL_OPTION_CHILDPLANE;
+    auto p = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != p);
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncplane_destroy(p));
     CHECK(0 == ncvisual_resize(ncv, 20, 20));
-    p = ncvisual_render(nc_, ncv, &vopts);
+    p = ncvisual_blit(nc_, ncv, &vopts);
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncplane_destroy(p));
-    p = ncvisual_render(nc_, ncv, &vopts);
+    p = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != p);
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncplane_destroy(p));
     CHECK(0 == ncvisual_rotate(ncv, M_PI / 2));
-    p = ncvisual_render(nc_, ncv, &vopts);
+    p = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != p);
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncplane_destroy(p));
@@ -58,7 +60,10 @@ TEST_CASE("Media") {
     CHECK(dimx == frame->width); FIXME */
     struct ncvisual_options opts{};
     opts.scaling = NCSCALE_STRETCH;
-    auto newn = ncvisual_render(nc_, ncv, &opts);
+    opts.n = n_;
+    opts.flags |= NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_VERALIGNED
+                   | NCVISUAL_OPTION_CHILDPLANE;
+    auto newn = ncvisual_blit(nc_, ncv, &opts);
     CHECK(newn);
     CHECK(0 == notcurses_render(nc_));
     CHECK(1 == ncvisual_decode(ncv));
@@ -76,7 +81,7 @@ TEST_CASE("Media") {
     struct ncvisual_options opts{};
     opts.scaling = NCSCALE_STRETCH;
     opts.n = ncp_;
-    CHECK(ncvisual_render(nc_, ncv, &opts));
+    CHECK(ncvisual_blit(nc_, ncv, &opts));
     CHECK(0 == notcurses_render(nc_));
     CHECK(1 == ncvisual_decode(ncv));
     ncvisual_destroy(ncv);
@@ -91,13 +96,13 @@ TEST_CASE("Media") {
     struct ncvisual_options opts{};
     opts.n = ncp_;
     CHECK(0 == ncvisual_blitter_geom(nc_, ncv, &opts, &odimy, &odimx, nullptr, nullptr, nullptr));
-    CHECK(ncvisual_render(nc_, ncv, &opts));
+    CHECK(ncvisual_blit(nc_, ncv, &opts));
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncvisual_resize_noninterpolative(ncv, ncv->pixy * 2, ncv->pixx * 2));
     CHECK(0 == ncvisual_blitter_geom(nc_, ncv, &opts, &ndimy, &ndimx, nullptr, nullptr, nullptr));
     CHECK(ndimy == odimy * 2);
     CHECK(ndimx == odimx * 2);
-    CHECK(ncvisual_render(nc_, ncv, &opts));
+    CHECK(ncvisual_blit(nc_, ncv, &opts));
     CHECK(0 == notcurses_render(nc_));
     ncvisual_destroy(ncv);
   }
@@ -110,7 +115,7 @@ TEST_CASE("Media") {
     struct ncvisual_options opts{};
     opts.n = ncp_;
     opts.scaling = NCSCALE_STRETCH;
-    CHECK(ncvisual_render(nc_, ncv, &opts));
+    CHECK(ncvisual_blit(nc_, ncv, &opts));
     void* needle = malloc(1);
     REQUIRE(nullptr != needle);
     struct ncplane* newn = ncplane_dup(ncp_, needle);
@@ -142,7 +147,7 @@ TEST_CASE("Media") {
         opts.scaling = NCSCALE_SCALE_HIRES;
         opts.n = ncp_;
         opts.blitter = NCBLIT_1x1;
-        CHECK(ncvisual_render(nc_, ncv, &opts));
+        CHECK(ncvisual_blit(nc_, ncv, &opts));
         CHECK(0 == notcurses_render(nc_));
       }
       ncvisual_destroy(ncv);
@@ -165,7 +170,7 @@ TEST_CASE("Media") {
         opts.scaling = NCSCALE_SCALE_HIRES;
         opts.n = ncp_;
         opts.blitter = NCBLIT_2x1;
-        CHECK(ncvisual_render(nc_, ncv, &opts));
+        CHECK(ncvisual_blit(nc_, ncv, &opts));
         CHECK(0 == notcurses_render(nc_));
       }
       ncvisual_destroy(ncv);
@@ -189,7 +194,7 @@ TEST_CASE("Media") {
         opts.scaling = NCSCALE_SCALE_HIRES;
         opts.n = ncp_;
         opts.blitter = NCBLIT_2x2;
-        CHECK(ncvisual_render(nc_, ncv, &opts));
+        CHECK(ncvisual_blit(nc_, ncv, &opts));
         CHECK(0 == notcurses_render(nc_));
       }
       ncvisual_destroy(ncv);
@@ -212,7 +217,7 @@ TEST_CASE("Media") {
         opts.scaling = NCSCALE_SCALE_HIRES;
         opts.n = ncp_;
         opts.blitter = NCBLIT_3x2;
-        CHECK(ncvisual_render(nc_, ncv, &opts));
+        CHECK(ncvisual_blit(nc_, ncv, &opts));
         CHECK(0 == notcurses_render(nc_));
       }
       ncvisual_destroy(ncv);
@@ -235,7 +240,7 @@ TEST_CASE("Media") {
         opts.scaling = NCSCALE_SCALE_HIRES;
         opts.n = ncp_;
         opts.blitter = NCBLIT_BRAILLE;
-        CHECK(ncvisual_render(nc_, ncv, &opts));
+        CHECK(ncvisual_blit(nc_, ncv, &opts));
         CHECK(0 == notcurses_render(nc_));
       }
       ncvisual_destroy(ncv);
@@ -261,7 +266,7 @@ TEST_CASE("Media") {
           opts.scaling = NCSCALE_SCALE;
           opts.blitter = NCBLIT_PIXEL;
           opts.n = n;
-          n = ncvisual_render(nc_, ncv, &opts);
+          n = ncvisual_blit(nc_, ncv, &opts);
           REQUIRE(nullptr != n);
           CHECK(0 == notcurses_render(nc_));
         }
@@ -288,7 +293,7 @@ TEST_CASE("Media") {
           struct ncvisual_options opts{};
           opts.scaling = NCSCALE_SCALE;
           opts.blitter = NCBLIT_PIXEL;
-          REQUIRE(ncvisual_render(nc_, ncv, &opts));
+          REQUIRE(ncvisual_blit(nc_, ncv, &opts));
           CHECK(0 == notcurses_render(nc_));
         }
         ncvisual_destroy(ncv);
@@ -310,13 +315,13 @@ TEST_CASE("Media") {
       CHECK(1 == ret);
       ret = ncvisual_decode_loop(ncv);
       CHECK(1 == ret);
-      struct ncplane* ncp = ncvisual_render(nc_, ncv, nullptr);
+      struct ncplane* ncp = ncvisual_blit(nc_, ncv, nullptr);
       CHECK(nullptr != ncp);
       CHECK(0 == ncplane_destroy(ncp));
       // FIXME verify that it is first frame, not last?
       ret = ncvisual_decode_loop(ncv);
       CHECK(0 == ret);
-      ncp = ncvisual_render(nc_, ncv, nullptr);
+      ncp = ncvisual_blit(nc_, ncv, nullptr);
       CHECK(nullptr != ncp);
       CHECK(0 == ncplane_destroy(ncp));
       ncvisual_destroy(ncv);
@@ -334,7 +339,7 @@ TEST_CASE("Media") {
       CHECK(dimx == frame->width); FIXME */
       struct ncvisual_options opts{};
       opts.scaling = NCSCALE_STRETCH;
-      auto newn = ncvisual_render(nc_, ncv, &opts);
+      auto newn = ncvisual_blit(nc_, ncv, &opts);
       CHECK(newn);
       CHECK(0 == notcurses_render(nc_));
       CHECK(0 == ncplane_destroy(newn));
@@ -363,7 +368,7 @@ TEST_CASE("Media") {
     vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
     auto ncv = ncvisual_from_file(find_data("onedot.png").get());
     REQUIRE(ncv);
-    auto child = ncvisual_render(nc_, ncv, &vopts);
+    auto child = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(child);
     CHECK(5 == ncplane_dim_y(child));
     CHECK(5 == ncplane_dim_x(child));
diff --git a/src/tests/rotate.cpp b/src/tests/rotate.cpp
index 6a24aa1dfc..258b25003a 100644
--- a/src/tests/rotate.cpp
+++ b/src/tests/rotate.cpp
@@ -171,7 +171,9 @@ TEST_CASE("Rotate") {
     auto ncv = ncvisual_from_rgba(rgba.data(), height, width * 4, width);
     REQUIRE(ncv);
     ncvisual_options opts{};
-    auto rendered = ncvisual_render(nc_, ncv, &opts);
+    opts.n = n_;
+    opts.flags = NCVISUAL_OPTION_CHILDPLANE;
+    auto rendered = ncvisual_blit(nc_, ncv, &opts);
     REQUIRE(rendered);
     int pxdimy, pxdimx;
     uint32_t* rgbaret = ncplane_as_rgba(rendered, NCBLIT_2x1,
@@ -207,16 +209,16 @@ TEST_CASE("Rotate") {
       opts.n = rendered;
       // FIXME check pixels after all rotations
       CHECK(0 == ncvisual_rotate(ncv, M_PI / 2));
-      CHECK(ncvisual_render(nc_, ncv, &opts));
+      CHECK(ncvisual_blit(nc_, ncv, &opts));
       CHECK(0 == notcurses_render(nc_));
       CHECK(0 == ncvisual_rotate(ncv, M_PI / 2));
-      CHECK(ncvisual_render(nc_, ncv, &opts));
+      CHECK(ncvisual_blit(nc_, ncv, &opts));
       CHECK(0 == notcurses_render(nc_));
       CHECK(0 == ncvisual_rotate(ncv, M_PI / 2));
-      CHECK(ncvisual_render(nc_, ncv, &opts));
+      CHECK(ncvisual_blit(nc_, ncv, &opts));
       CHECK(0 == notcurses_render(nc_));
       CHECK(0 == ncvisual_rotate(ncv, M_PI / 2));
-      CHECK(ncvisual_render(nc_, ncv, &opts));
+      CHECK(ncvisual_blit(nc_, ncv, &opts));
       CHECK(0 == notcurses_render(nc_));
     }
     ncvisual_destroy(ncv);
@@ -231,7 +233,9 @@ TEST_CASE("Rotate") {
     auto ncv = ncvisual_from_rgba(rgba.data(), height, width * 4, width);
     REQUIRE(ncv);
     ncvisual_options opts{};
-    auto rendered = ncvisual_render(nc_, ncv, &opts);
+    opts.n = n_;
+    opts.flags = NCVISUAL_OPTION_CHILDPLANE;
+    auto rendered = ncvisual_blit(nc_, ncv, &opts);
     REQUIRE(rendered);
     int pxdimy, pxdimx;
     uint32_t* rgbaret = ncplane_as_rgba(rendered, NCBLIT_2x1,
@@ -267,16 +271,16 @@ TEST_CASE("Rotate") {
       // FIXME check pixels after all rotations
       opts.n = rendered;
       CHECK(0 == ncvisual_rotate(ncv, -M_PI / 2));
-      CHECK(ncvisual_render(nc_, ncv, &opts));
+      CHECK(ncvisual_blit(nc_, ncv, &opts));
       CHECK(0 == notcurses_render(nc_));
       CHECK(0 == ncvisual_rotate(ncv, -M_PI / 2));
-      CHECK(ncvisual_render(nc_, ncv, &opts));
+      CHECK(ncvisual_blit(nc_, ncv, &opts));
       CHECK(0 == notcurses_render(nc_));
       CHECK(0 == ncvisual_rotate(ncv, -M_PI / 2));
-      CHECK(ncvisual_render(nc_, ncv, &opts));
+      CHECK(ncvisual_blit(nc_, ncv, &opts));
       CHECK(0 == notcurses_render(nc_));
       CHECK(0 == ncvisual_rotate(ncv, -M_PI / 2));
-      CHECK(ncvisual_render(nc_, ncv, &opts));
+      CHECK(ncvisual_blit(nc_, ncv, &opts));
       CHECK(0 == notcurses_render(nc_));
     }
     ncvisual_destroy(ncv);
diff --git a/src/tests/selector.cpp b/src/tests/selector.cpp
index 5957317ec1..02cc4c285e 100644
--- a/src/tests/selector.cpp
+++ b/src/tests/selector.cpp
@@ -109,10 +109,10 @@ TEST_CASE("Selectors") {
 
   SUBCASE("PopulatedSelector") {
     ncselector_item items[] = {
-      { strdup("op1"), strdup("this is option 1"), 0, 0, },
-      { strdup("2ndop"), strdup("this is option #2"), 0, 0, },
-      { strdup("tres"), strdup("option the third"), 0, 0, },
-      { NULL, NULL, 0, 0, },
+      { strdup("op1"), strdup("this is option 1"), },
+      { strdup("2ndop"), strdup("this is option #2"), },
+      { strdup("tres"), strdup("option the third"), },
+      { NULL, NULL, },
     };
     struct ncselector_options opts{};
     opts.items = items;
@@ -164,10 +164,10 @@ TEST_CASE("Selectors") {
 
   SUBCASE("SelectorMovement") {
     ncselector_item items[] = {
-      { strdup("op1"), strdup("this is option 1"), 0, 0, },
-      { strdup("2ndop"), strdup("this is option #2"), 0, 0, },
-      { strdup("tres"), strdup("option the third"), 0, 0, },
-      { NULL, NULL, 0, 0, },
+      { strdup("op1"), strdup("this is option 1"), },
+      { strdup("2ndop"), strdup("this is option #2"), },
+      { strdup("tres"), strdup("option the third"), },
+      { NULL, NULL, },
     };
     struct ncselector_options opts{};
     opts.items = items;
@@ -210,10 +210,10 @@ TEST_CASE("Selectors") {
   // Provide three items, limited to 1 shown at a time
   SUBCASE("ScrollingSelectorOne") {
     ncselector_item items[] = {
-      { strdup("op1"), strdup("this is option 1"), 0, 0, },
-      { strdup("2ndop"), strdup("this is option #2"), 0, 0, },
-      { strdup("tres"), strdup("option the third"), 0, 0, },
-      { NULL, NULL, 0, 0, },
+      { strdup("op1"), strdup("this is option 1"), },
+      { strdup("2ndop"), strdup("this is option #2"), },
+      { strdup("tres"), strdup("option the third"), },
+      { NULL, NULL, },
     };
     struct ncselector_options opts{};
     opts.maxdisplay = 1;
@@ -262,10 +262,10 @@ TEST_CASE("Selectors") {
   // Provide three items, limited to 2 shown at a time
   SUBCASE("ScrollingSelectorTwo") {
     ncselector_item items[] = {
-      { strdup("op1"), strdup("this is option 1"), 0, 0, },
-      { strdup("2ndop"), strdup("this is option #2"), 0, 0, },
-      { strdup("tres"), strdup("option the third"), 0, 0, },
-      { NULL, NULL, 0, 0, },
+      { strdup("op1"), strdup("this is option 1"), },
+      { strdup("2ndop"), strdup("this is option #2"), },
+      { strdup("tres"), strdup("option the third"), },
+      { NULL, NULL, },
     };
     struct ncselector_options opts{};
     opts.maxdisplay = 2;
diff --git a/src/tests/sixel.cpp b/src/tests/sixel.cpp
index 500ec76b16..84c9a16f9f 100644
--- a/src/tests/sixel.cpp
+++ b/src/tests/sixel.cpp
@@ -162,9 +162,10 @@ TEST_CASE("Sixels") {
     auto ncv = ncvisual_from_file(find_data("worldmap.png").get());
     REQUIRE(ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto newn = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto newn = ncvisual_blit(nc_, ncv, &vopts);
     CHECK(newn);
     CHECK(0 == notcurses_render(nc_));
     auto rgb = sixel_to_rgb(newn->sprite->glyph.buf, newn->sprite->glyph.used,
@@ -184,9 +185,10 @@ TEST_CASE("Sixels") {
     auto ncv = ncvisual_from_file(find_data("natasha-blur.png").get());
     REQUIRE(ncv);
     struct ncvisual_options vopts{};
+    vopts.n = n_;
     vopts.blitter = NCBLIT_PIXEL;
-    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-    auto newn = ncvisual_render(nc_, ncv, &vopts);
+    vopts.flags = NCVISUAL_OPTION_NODEGRADE | NCVISUAL_OPTION_CHILDPLANE;
+    auto newn = ncvisual_blit(nc_, ncv, &vopts);
     CHECK(newn);
     auto rgbold = sixel_to_rgb(newn->sprite->glyph.buf, newn->sprite->glyph.used,
                                newn->sprite->pixy, newn->sprite->pixx);
diff --git a/src/tests/stacking.cpp b/src/tests/stacking.cpp
index 1d4ec4ec95..54f4aeb02e 100644
--- a/src/tests/stacking.cpp
+++ b/src/tests/stacking.cpp
@@ -45,7 +45,7 @@ TEST_CASE("Stacking") {
       .leny = 2, .lenx = 1, .blitter = NCBLIT_2x1, .flags = 0,
       .transcolor = 0, .pxoffy = 0, .pxoffx = 0,
     };
-    CHECK(top == ncvisual_render(nc_, ncv, &vopts));
+    CHECK(top == ncvisual_blit(nc_, ncv, &vopts));
     ncvisual_destroy(ncv);
 
     // create an ncvisual of 2 rows, 1 column, with the top 0xffffff
@@ -53,19 +53,24 @@ TEST_CASE("Stacking") {
     ncv = ncvisual_from_rgba(botv, 2, 4, 1);
     REQUIRE(nullptr != ncv);
     vopts.n = n_;
-    CHECK(n_ == ncvisual_render(nc_, ncv, &vopts));
+    vopts.flags |= NCVISUAL_OPTION_CHILDPLANE;
+    auto newn = ncvisual_blit(nc_, ncv, &vopts);
+    REQUIRE(nullptr != newn);
     ncvisual_destroy(ncv);
+    ncplane_move_below(newn, top);
 
     CHECK(0 == notcurses_render(nc_));
     uint64_t channels;
     auto egc = notcurses_at_yx(nc_, 0, 0, nullptr, &channels);
     REQUIRE(nullptr != egc);
+notcurses_debug(nc_, stderr);
     // ought yield space with white background FIXME currently just yields
     // a lower half block
     CHECK(0 == strcmp("\u2584", egc));
     CHECK(0xffffff == ncchannels_fg_rgb(channels));
     CHECK(0xffffff == ncchannels_bg_rgb(channels));
-    ncplane_destroy(top);
+    CHECK(0 == ncplane_destroy(top));
+    CHECK(0 == ncplane_destroy(newn));
   }
 
   SUBCASE("UpperAtopLowerWhite") {
@@ -83,7 +88,7 @@ TEST_CASE("Stacking") {
       .leny = 2, .lenx = 1, .blitter = NCBLIT_2x1, .flags = 0,
       .transcolor = 0, .pxoffy = 0, .pxoffx = 0,
     };
-    CHECK(top == ncvisual_render(nc_, ncv, &vopts));
+    CHECK(top == ncvisual_blit(nc_, ncv, &vopts));
     ncvisual_destroy(ncv);
 
     // create an ncvisual of 2 rows, 1 column, with the bottom 0xffffff
@@ -91,8 +96,11 @@ TEST_CASE("Stacking") {
     ncv = ncvisual_from_rgba(botv, 2, 4, 1);
     REQUIRE(nullptr != ncv);
     vopts.n = n_;
-    CHECK(n_ == ncvisual_render(nc_, ncv, &vopts));
+    vopts.flags |= NCVISUAL_OPTION_CHILDPLANE;
+    auto newn = ncvisual_blit(nc_, ncv, &vopts);
+    REQUIRE(nullptr != newn);
     ncvisual_destroy(ncv);
+    ncplane_move_below(newn, top);
 
     CHECK(0 == notcurses_render(nc_));
     uint64_t channels;
@@ -103,7 +111,8 @@ TEST_CASE("Stacking") {
     CHECK(0 == strcmp("\u2580", egc));
     CHECK(0xffffff == ncchannels_fg_rgb(channels));
     CHECK(0xffffff == ncchannels_bg_rgb(channels));
-    ncplane_destroy(top);
+    CHECK(0 == ncplane_destroy(top));
+    CHECK(0 == ncplane_destroy(newn));
   }
 
   SUBCASE("StackedQuadHalves") {
@@ -122,7 +131,7 @@ TEST_CASE("Stacking") {
         .leny = 2, .lenx = 2, .blitter = NCBLIT_2x2, .flags = 0,
         .transcolor = 0, .pxoffy = 0, .pxoffx = 0,
       };
-      CHECK(top == ncvisual_render(nc_, ncv, &vopts));
+      CHECK(top == ncvisual_blit(nc_, ncv, &vopts));
       ncvisual_destroy(ncv);
 
       // create an ncvisual of 2 rows, 2 columns, with the bottom 0xffffff
@@ -130,8 +139,11 @@ TEST_CASE("Stacking") {
       ncv = ncvisual_from_rgba(botv, 2, 8, 2);
       REQUIRE(nullptr != ncv);
       vopts.n = n_;
-      CHECK(n_ == ncvisual_render(nc_, ncv, &vopts));
+      vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
+      auto newn = ncvisual_blit(nc_, ncv, &vopts); 
+      REQUIRE(nullptr != newn);
       ncvisual_destroy(ncv);
+      ncplane_move_below(newn, top);
 
       CHECK(0 == notcurses_render(nc_));
       uint64_t channels;
@@ -142,7 +154,8 @@ TEST_CASE("Stacking") {
       CHECK(0 == strcmp("\u2580", egc));
       CHECK(0x00ff00 == ncchannels_fg_rgb(channels));
       CHECK(0x00ff00 == ncchannels_bg_rgb(channels));
-      ncplane_destroy(top);
+      CHECK(0 == ncplane_destroy(top));
+      CHECK(0 == ncplane_destroy(newn));
     }
   }
 
@@ -164,7 +177,7 @@ TEST_CASE("Stacking") {
         .leny = 2, .lenx = 2, .blitter = NCBLIT_2x2, .flags = 0,
         .transcolor = 0, .pxoffy = 0, .pxoffx = 0,
       };
-      CHECK(top == ncvisual_render(nc_, ncv, &vopts));
+      CHECK(top == ncvisual_blit(nc_, ncv, &vopts));
       ncvisual_destroy(ncv);
 
       // create an ncvisual of 2 rows, 2 columns, with the tr, bl 0xffffff
@@ -172,8 +185,11 @@ TEST_CASE("Stacking") {
       ncv = ncvisual_from_rgba(botv, 2, 8, 2);
       REQUIRE(nullptr != ncv);
       vopts.n = n_;
-      CHECK(n_ == ncvisual_render(nc_, ncv, &vopts));
+      vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
+      auto newn = ncvisual_blit(nc_, ncv, &vopts);
+      REQUIRE(nullptr != newn);
       ncvisual_destroy(ncv);
+      ncplane_move_below(newn, top);
 
       CHECK(0 == notcurses_render(nc_));
       uint64_t channels;
@@ -184,7 +200,8 @@ TEST_CASE("Stacking") {
       CHECK(0 == strcmp("\u259a", egc)); // quadrant upper left and lower right
       CHECK(0xffffff == ncchannels_fg_rgb(channels));
       CHECK(0xffffff == ncchannels_bg_rgb(channels));
-      ncplane_destroy(top);
+      CHECK(0 == ncplane_destroy(top));
+      CHECK(0 == ncplane_destroy(newn));
     }
   }
 
diff --git a/src/tests/visual.cpp b/src/tests/visual.cpp
index 9c452d0754..88087c0743 100644
--- a/src/tests/visual.cpp
+++ b/src/tests/visual.cpp
@@ -74,18 +74,20 @@ TEST_CASE("Visual") {
     struct ncvisual_options vopts{};
     vopts.x = NCALIGN_CENTER;
     vopts.y = NCALIGN_CENTER;
-    vopts.flags |= NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_VERALIGNED;
-    auto p = ncvisual_render(nc_, ncv, &vopts);
+    vopts.n = n_;
+    vopts.flags |= NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_VERALIGNED
+                    | NCVISUAL_OPTION_CHILDPLANE;
+    auto p = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != p);
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncplane_destroy(p));
     CHECK(0 == ncvisual_resize(ncv, 20, 20));
-    p = ncvisual_render(nc_, ncv, &vopts);
+    p = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != p);
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncplane_destroy(p));
     CHECK(0 == ncvisual_rotate(ncv, M_PI / 2));
-    p = ncvisual_render(nc_, ncv, &vopts);
+    p = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != p);
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncplane_destroy(p));
@@ -96,7 +98,7 @@ TEST_CASE("Visual") {
   SUBCASE("VisualAligned") {
     const uint32_t pixels[4] = { htole(0xffff0000), htole(0xff00ff00), htole(0xff0000ff), htole(0xffffffff) };
     ncvisual_options vopts = {
-      .n = nullptr,
+      .n = n_,
       .scaling = NCSCALE_NONE,
       .y = 0,
       .x = NCALIGN_LEFT,
@@ -105,13 +107,13 @@ TEST_CASE("Visual") {
       .leny = 2,
       .lenx = 2,
       .blitter = NCBLIT_1x1,
-      .flags = NCVISUAL_OPTION_HORALIGNED,
+      .flags = NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_CHILDPLANE,
       .transcolor = 0,
       .pxoffy = 0, .pxoffx = 0,
     };
     auto ncv = ncvisual_from_rgba(pixels, 2, 2 * sizeof(*pixels), 2);
     REQUIRE(nullptr != ncv);
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     CHECK(0 == notcurses_render(nc_));
     ncvisual_destroy(ncv);
@@ -126,16 +128,17 @@ TEST_CASE("Visual") {
     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts = {
-      .n = nullptr,
+      .n = n_,
       .scaling = NCSCALE_NONE,
       .y = 0, .x = 0,
       .begy = 0, .begx = 0,
       .leny = 5, .lenx = 8,
       .blitter = NCBLIT_1x1,
-      .flags = 0, .transcolor = 0,
+      .flags = NCVISUAL_OPTION_CHILDPLANE,
+      .transcolor = 0,
       .pxoffy = 0, .pxoffx = 0,
     };
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     CHECK(5 == ncplane_dim_y(n));
     CHECK(8 == ncplane_dim_x(n));
@@ -152,16 +155,17 @@ TEST_CASE("Visual") {
     auto ncv = ncvisual_from_rgba(v.data(), 1, sizeof(decltype(v)::value_type), 1);
     REQUIRE(nullptr != ncv);
     struct ncvisual_options vopts = {
-      .n = nullptr,
+      .n = n_,
       .scaling = NCSCALE_STRETCH,
       .y = 0, .x = 0,
       .begy = 0, .begx = 0,
       .leny = 0, .lenx = 0,
       .blitter = NCBLIT_1x1,
-      .flags = 0, .transcolor = 0,
+      .flags = NCVISUAL_OPTION_CHILDPLANE,
+      .transcolor = 0,
       .pxoffy = 0, .pxoffx = 0,
     };
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     CHECK(0 == notcurses_render(nc_));
     REQUIRE(nullptr != n);
     CHECK(dimy == ncplane_dim_y(n));
@@ -198,7 +202,7 @@ TEST_CASE("Visual") {
       .flags = 0, .transcolor = 0,
       .pxoffy = 0, .pxoffx = 0,
     };
-    auto n = ncvisual_render(nc_, ncv, &vopts);
+    auto n = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(nullptr != n);
     CHECK(5 == ncplane_dim_y(n));
     CHECK(8 == ncplane_dim_x(n));
@@ -222,7 +226,7 @@ TEST_CASE("Visual") {
       .flags = 0, .transcolor = 0,
       .pxoffy = 0, .pxoffx = 0,
     };
-    auto newn = ncvisual_render(nc_, ncv, &vopts);
+    auto newn = ncvisual_blit(nc_, ncv, &vopts);
     CHECK(0 == notcurses_render(nc_));
     CHECK(0 == ncvisual_resize_noninterpolative(ncv, ncv->pixy * 3, ncv->pixx * 3));
     CHECK(6 == ncv->pixy);
@@ -244,7 +248,7 @@ TEST_CASE("Visual") {
       }
     }
     REQUIRE(newn);
-    auto enewn = ncvisual_render(nc_, ncv, &vopts);
+    auto enewn = ncvisual_blit(nc_, ncv, &vopts);
     int newy, newx;
     ncplane_dim_yx(enewn, &newy, &newx);
     CHECK(6 == newy);
@@ -265,7 +269,7 @@ TEST_CASE("Visual") {
     struct ncvisual_options opts{};
     opts.blitter = NCBLIT_1x1;
     opts.n = ncp_;
-    CHECK(ncp_ == ncvisual_render(nc_, ncv, &opts));
+    CHECK(ncp_ == ncvisual_blit(nc_, ncv, &opts));
     CHECK(0 == notcurses_render(nc_));
     for(int y = 0 ; y < dimy ; ++y){
       for(int x = 0 ; x < dimx ; ++x){
@@ -293,7 +297,7 @@ TEST_CASE("Visual") {
     struct ncvisual_options opts{};
     opts.blitter = NCBLIT_1x1;
     opts.n = ncp_;
-    CHECK(nullptr != ncvisual_render(nc_, ncv, &opts));
+    CHECK(nullptr != ncvisual_blit(nc_, ncv, &opts));
     CHECK(0 == notcurses_render(nc_));
     for(int y = 0 ; y < dimy ; ++y){
       for(int x = 0 ; x < dimx ; ++x){
@@ -333,7 +337,7 @@ TEST_CASE("Visual") {
       vopts.n = n_;
       vopts.blitter = NCBLIT_2x1;
       vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-      CHECK(n_ == ncvisual_render(nc_, ncv, &vopts));
+      CHECK(n_ == ncvisual_blit(nc_, ncv, &vopts));
       CHECK(0 == notcurses_render(nc_));
       for(int y = 0 ; y < DIMY / 2 ; ++y){
         for(int x = 0 ; x < DIMX ; ++x){
@@ -373,7 +377,7 @@ TEST_CASE("Visual") {
       vopts.n = n_;
       vopts.blitter = NCBLIT_2x2;
       vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-      CHECK(n_ == ncvisual_render(nc_, ncv, &vopts));
+      CHECK(n_ == ncvisual_blit(nc_, ncv, &vopts));
       CHECK(0 == notcurses_render(nc_));
       for(int y = 0 ; y < DIMY / 2 ; ++y){
         for(int x = 0 ; x < DIMX / 2 ; ++x){
@@ -431,7 +435,7 @@ TEST_CASE("Visual") {
       vopts.n = n_;
       vopts.blitter = NCBLIT_2x2;
       vopts.flags = NCVISUAL_OPTION_NODEGRADE;
-      CHECK(n_ == ncvisual_render(nc_, ncv, &vopts));
+      CHECK(n_ == ncvisual_blit(nc_, ncv, &vopts));
       CHECK(0 == notcurses_render(nc_));
       for(int y = 0 ; y < DIMY / 2 ; ++y){
         for(int x = 0 ; x < DIMX / 2 ; ++x){
@@ -471,7 +475,7 @@ TEST_CASE("Visual") {
       auto ncv = ncvisual_from_rgba(pixels, 2, 2 * sizeof(*pixels), 2);
       REQUIRE(nullptr != ncv);
       struct ncvisual_options vopts = {
-        .n = nullptr,
+        .n = n_,
         .scaling = NCSCALE_NONE,
         .y = 0,
         .x = 0,
@@ -480,11 +484,11 @@ TEST_CASE("Visual") {
         .leny = 0,
         .lenx = 0,
         .blitter = NCBLIT_2x2,
-        .flags = 0,
+        .flags = NCVISUAL_OPTION_CHILDPLANE,
         .transcolor = 0,
         .pxoffy = 0, .pxoffx = 0,
       };
-      auto ncvp = ncvisual_render(nc_, ncv, &vopts);
+      auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
       REQUIRE(nullptr != ncvp);
       int dimy, dimx;
       ncplane_dim_yx(ncvp, &dimy, &dimx);
@@ -516,7 +520,7 @@ TEST_CASE("Visual") {
         auto ncv = ncvisual_from_rgba(pixels[i], 2, 2 * sizeof(**pixels), 2);
         REQUIRE(nullptr != ncv);
         struct ncvisual_options vopts = {
-          .n = nullptr,
+          .n = n_,
           .scaling = NCSCALE_NONE,
           .y = 0,
           .x = 0,
@@ -525,11 +529,11 @@ TEST_CASE("Visual") {
           .leny = 0,
           .lenx = 0,
           .blitter = NCBLIT_2x2,
-          .flags = 0,
+          .flags = NCVISUAL_OPTION_CHILDPLANE,
           .transcolor = 0,
           .pxoffy = 0, .pxoffx = 0,
         };
-        auto ncvp = ncvisual_render(nc_, ncv, &vopts);
+        auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
         REQUIRE(nullptr != ncvp);
         int dimy, dimx;
         ncplane_dim_yx(ncvp, &dimy, &dimx);
@@ -564,7 +568,7 @@ TEST_CASE("Visual") {
         auto ncv = ncvisual_from_rgba(pixels[i], 2, 2 * sizeof(**pixels), 2);
         REQUIRE(nullptr != ncv);
         struct ncvisual_options vopts = {
-          .n = nullptr,
+          .n = n_,
           .scaling = NCSCALE_NONE,
           .y = 0,
           .x = 0,
@@ -573,11 +577,11 @@ TEST_CASE("Visual") {
           .leny = 0,
           .lenx = 0,
           .blitter = NCBLIT_2x2,
-          .flags = 0,
+          .flags = NCVISUAL_OPTION_CHILDPLANE,
           .transcolor = 0,
           .pxoffy = 0, .pxoffx = 0,
         };
-        auto ncvp = ncvisual_render(nc_, ncv, &vopts);
+        auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
         REQUIRE(nullptr != ncvp);
         int dimy, dimx;
         ncplane_dim_yx(ncvp, &dimy, &dimx);
@@ -617,7 +621,7 @@ TEST_CASE("Visual") {
         auto ncv = ncvisual_from_rgba(pixels[i], 2, 2 * sizeof(**pixels), 2);
         REQUIRE(nullptr != ncv);
         struct ncvisual_options vopts = {
-          .n = nullptr,
+          .n = n_,
           .scaling = NCSCALE_NONE,
           .y = 0,
           .x = 0,
@@ -626,11 +630,11 @@ TEST_CASE("Visual") {
           .leny = 0,
           .lenx = 0,
           .blitter = NCBLIT_2x2,
-          .flags = 0,
+          .flags = NCVISUAL_OPTION_CHILDPLANE,
           .transcolor = 0,
           .pxoffy = 0, .pxoffx = 0,
         };
-        auto ncvp = ncvisual_render(nc_, ncv, &vopts);
+        auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
         REQUIRE(nullptr != ncvp);
         int dimy, dimx;
         ncplane_dim_yx(ncvp, &dimy, &dimx);
@@ -669,7 +673,7 @@ TEST_CASE("Visual") {
         auto ncv = ncvisual_from_rgba(pixels[i], 2, 2 * sizeof(**pixels), 2);
         REQUIRE(nullptr != ncv);
         struct ncvisual_options vopts = {
-          .n = nullptr,
+          .n = n_,
           .scaling = NCSCALE_NONE,
           .y = 0,
           .x = 0,
@@ -678,11 +682,11 @@ TEST_CASE("Visual") {
           .leny = 0,
           .lenx = 0,
           .blitter = NCBLIT_2x2,
-          .flags = 0,
+          .flags = NCVISUAL_OPTION_CHILDPLANE,
           .transcolor = 0,
           .pxoffy = 0, .pxoffx = 0,
         };
-        auto ncvp = ncvisual_render(nc_, ncv, &vopts);
+        auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
         REQUIRE(nullptr != ncvp);
         int dimy, dimx;
         ncplane_dim_yx(ncvp, &dimy, &dimx);
@@ -702,7 +706,7 @@ TEST_CASE("Visual") {
     }
   }
 
-  // test NCVISUAL_OPTIONS_CHILDPLANE + stretch + null alignment
+  // test NCVISUAL_OPTION_CHILDPLANE + stretch + null alignment
   SUBCASE("ImageChildScaling") {
     struct ncplane_options opts = {
       .y = 0, .x = 0,
@@ -717,14 +721,14 @@ TEST_CASE("Visual") {
     auto parent = ncplane_create(n_, &opts);
     REQUIRE(parent);
     struct ncvisual_options vopts = {
-      .n = nullptr,
+      .n = n_,
       .scaling = NCSCALE_NONE,
       .y = 0,
       .x = 0,
       .begy = 0, .begx = 0,
       .leny = 0, .lenx = 0,
       .blitter = NCBLIT_1x1,
-      .flags = 0,
+      .flags = NCVISUAL_OPTION_CHILDPLANE,
       .transcolor = 0,
       .pxoffy = 0, .pxoffx = 0,
     };
@@ -736,7 +740,7 @@ TEST_CASE("Visual") {
     };
     auto ncv = ncvisual_from_rgba(pixels, 4, 16, 4);
     REQUIRE(ncv);
-    auto child = ncvisual_render(nc_, ncv, &vopts);
+    auto child = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(child);
     CHECK(4 == ncplane_dim_y(child));
     CHECK(4 == ncplane_dim_x(child));
@@ -747,7 +751,7 @@ TEST_CASE("Visual") {
     vopts.n = parent,
     vopts.scaling = NCSCALE_STRETCH,
     vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
-    child = ncvisual_render(nc_, ncv, &vopts);
+    child = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(child);
     CHECK(20 == ncplane_dim_y(child));
     CHECK(20 == ncplane_dim_x(child));
@@ -787,7 +791,7 @@ TEST_CASE("Visual") {
     const uint32_t pixels[1] = { htole(0xffffffff) };
     auto ncv = ncvisual_from_rgba(pixels, 1, 4, 1);
     REQUIRE(ncv);
-    auto child = ncvisual_render(nc_, ncv, &vopts);
+    auto child = ncvisual_blit(nc_, ncv, &vopts);
     REQUIRE(child);
     CHECK(1 == ncplane_dim_y(child));
     CHECK(1 == ncplane_dim_x(child));