Repository: dankamongmen/notcurses (language: python)

Issue:
standardize on behavior of 0 when specifying geometries
A number of functions take geometries, with `ncplane_as_rgba()` as an example. Some take an upper-left and lower-right point; some take an origin and lengths. This disparity is bad enough, but then we also sometimes support 0,0 to indicate "everything" and sometimes require -1,-1 for same, with 0,0 being an error.

I'd like to normalize these before ABI3. Let's get a list of all entry points taking a geometry as input, and see if there are any which require/prefer one or the other form. If not (I can't imagine why they would), let's figure out whether 0,0 ought be an error, or "everything left", and the same for negative values.

Regarding this last, there's a discontinuity when we go to "everything" regardless of whether it's at -1,-1 or 0,0, since 1,1 gets at most 1 cell, and it's been decreasing from the maximum to get there. If all the negatives work, and 0,0 does as well (or if both classes are errors), there are only two total discontinuities. If one or the other works, there are 3. If the caller is using unsigneds and coercing them to ints, it makes more sense to consider all negatives an error, imho. If the caller is writing a natural algorithm, one that terminates at 0,0, it makes more sense to consider 0,0 an error imho. But such a caller would then need to check for the error, in which case they could just check for 0,0, and avoid the call. From a convenience perspective, having 0,0 be "everything left" seems more attrative imho than -1,-1, but the biggest issue for convenience is probably simply consistent handling of arguments.

I'm inclined to think negatives ought always be errors, so that overflow due to truncated unsigned parameters is always caught. If that's what we do, convenience demands that the 0,0 option remain for "everything else". So I'm inclined to handle it that way:

 - one or two negative arguments is an error
 - one zero argument is an error
 - two zero arguments means "specified origin through lower right"
 - two positive arguments means "specified area"
 - origin outside the target is an error
 - lower right, when specified, outside the target is an error
standardize on behavior of 0 when specifying geometries
A number of functions take geometries, with `ncplane_as_rgba()` as an example. Some take an upper-left and lower-right point; some take an origin and lengths. This disparity is bad enough, but then we also sometimes support 0,0 to indicate "everything" and sometimes require -1,-1 for same, with 0,0 being an error.

I'd like to normalize these before ABI3. Let's get a list of all entry points taking a geometry as input, and see if there are any which require/prefer one or the other form. If not (I can't imagine why they would), let's figure out whether 0,0 ought be an error, or "everything left", and the same for negative values.

Regarding this last, there's a discontinuity when we go to "everything" regardless of whether it's at -1,-1 or 0,0, since 1,1 gets at most 1 cell, and it's been decreasing from the maximum to get there. If all the negatives work, and 0,0 does as well (or if both classes are errors), there are only two total discontinuities. If one or the other works, there are 3. If the caller is using unsigneds and coercing them to ints, it makes more sense to consider all negatives an error, imho. If the caller is writing a natural algorithm, one that terminates at 0,0, it makes more sense to consider 0,0 an error imho. But such a caller would then need to check for the error, in which case they could just check for 0,0, and avoid the call. From a convenience perspective, having 0,0 be "everything left" seems more attrative imho than -1,-1, but the biggest issue for convenience is probably simply consistent handling of arguments.

I'm inclined to think negatives ought always be errors, so that overflow due to truncated unsigned parameters is always caught. If that's what we do, convenience demands that the 0,0 option remain for "everything else". So I'm inclined to handle it that way:

 - one or two negative arguments is an error
 - one zero argument is an error
 - two zero arguments means "specified origin through lower right"
 - two positive arguments means "specified area"
 - origin outside the target is an error
 - lower right, when specified, outside the target is an error
standardize on behavior of 0 when specifying geometries
A number of functions take geometries, with `ncplane_as_rgba()` as an example. Some take an upper-left and lower-right point; some take an origin and lengths. This disparity is bad enough, but then we also sometimes support 0,0 to indicate "everything" and sometimes require -1,-1 for same, with 0,0 being an error.

I'd like to normalize these before ABI3. Let's get a list of all entry points taking a geometry as input, and see if there are any which require/prefer one or the other form. If not (I can't imagine why they would), let's figure out whether 0,0 ought be an error, or "everything left", and the same for negative values.

Regarding this last, there's a discontinuity when we go to "everything" regardless of whether it's at -1,-1 or 0,0, since 1,1 gets at most 1 cell, and it's been decreasing from the maximum to get there. If all the negatives work, and 0,0 does as well (or if both classes are errors), there are only two total discontinuities. If one or the other works, there are 3. If the caller is using unsigneds and coercing them to ints, it makes more sense to consider all negatives an error, imho. If the caller is writing a natural algorithm, one that terminates at 0,0, it makes more sense to consider 0,0 an error imho. But such a caller would then need to check for the error, in which case they could just check for 0,0, and avoid the call. From a convenience perspective, having 0,0 be "everything left" seems more attrative imho than -1,-1, but the biggest issue for convenience is probably simply consistent handling of arguments.

I'm inclined to think negatives ought always be errors, so that overflow due to truncated unsigned parameters is always caught. If that's what we do, convenience demands that the 0,0 option remain for "everything else". So I'm inclined to handle it that way:

 - one or two negative arguments is an error
 - one zero argument is an error
 - two zero arguments means "specified origin through lower right"
 - two positive arguments means "specified area"
 - origin outside the target is an error
 - lower right, when specified, outside the target is an error
standardize on behavior of 0 when specifying geometries
A number of functions take geometries, with `ncplane_as_rgba()` as an example. Some take an upper-left and lower-right point; some take an origin and lengths. This disparity is bad enough, but then we also sometimes support 0,0 to indicate "everything" and sometimes require -1,-1 for same, with 0,0 being an error.

I'd like to normalize these before ABI3. Let's get a list of all entry points taking a geometry as input, and see if there are any which require/prefer one or the other form. If not (I can't imagine why they would), let's figure out whether 0,0 ought be an error, or "everything left", and the same for negative values.

Regarding this last, there's a discontinuity when we go to "everything" regardless of whether it's at -1,-1 or 0,0, since 1,1 gets at most 1 cell, and it's been decreasing from the maximum to get there. If all the negatives work, and 0,0 does as well (or if both classes are errors), there are only two total discontinuities. If one or the other works, there are 3. If the caller is using unsigneds and coercing them to ints, it makes more sense to consider all negatives an error, imho. If the caller is writing a natural algorithm, one that terminates at 0,0, it makes more sense to consider 0,0 an error imho. But such a caller would then need to check for the error, in which case they could just check for 0,0, and avoid the call. From a convenience perspective, having 0,0 be "everything left" seems more attrative imho than -1,-1, but the biggest issue for convenience is probably simply consistent handling of arguments.

I'm inclined to think negatives ought always be errors, so that overflow due to truncated unsigned parameters is always caught. If that's what we do, convenience demands that the 0,0 option remain for "everything else". So I'm inclined to handle it that way:

 - one or two negative arguments is an error
 - one zero argument is an error
 - two zero arguments means "specified origin through lower right"
 - two positive arguments means "specified area"
 - origin outside the target is an error
 - lower right, when specified, outside the target is an error


Discussion / hints:
relevant functions: `ncplane_resize()`, `ncplane_resize_simple()`, `ncplane_contents()`, `ncplane_hline_interp()`, `ncplane_vline_interp()`, `ncplane_box()`, `ncplane_gradient()`, `ncplane_highgradient()`, `ncplane_gradient_sized()`, `ncplane_highgradient_sized()`, `ncplane_mergedown()`, `ncplane_erase_region()`, `ncvisual_from_plane()`, `ncvisual_render()`, `ncplane_as_rgba()`, and their direct mode relatives.
Here's another question: `keepy` and `keepx` (wrt `ncplane_resize()`) must be within the old plane, but `keeplenx` and `keepleny` only need be within the new one. why wouldn't `keepleny + keepy` and `keeplenx + keepx` need to be within the old plane, as well? this leads to output like:

 ```
ncplane_resize_internal:742:70x80 @ 0/0 → 70/80 @ 0/0 (keeping 70x80 from 0/0)
ncplane_resize_internal:742:14x80 @ 0/0 → 14/80 @ 0/0 (keeping 14x80 from 0/0)
```

which looks wrong and jarring (how can i keep 70x80 from 0x0?), even if it's not actually leading to a false copy.
> ```
> ncplane_resize_internal:742:70x80 @ 0/0 → 70/80 @ 0/0 (keeping 70x80 from 0/0)
> ncplane_resize_internal:742:14x80 @ 0/0 → 14/80 @ 0/0 (keeping 14x80 from 0/0)
> ```
> which looks wrong and jarring (how can i keep 70x80 from 0x0?), even if it's not actually leading to a false copy.

replacing " from " with "@" clears up a lot of the confusion here
`resize`: upper-left corner of area to keep, geom to keep, upper-left corner of kept target area, geom to target (no all)
`resize_simple`: geom to target (no all)
`contents`: upper-left corner of area to return, geom of area (-1 for all)
`hline`: length of line (no all)
`vline`: length of line (no all)
`box`: lower-right corner of area to boxify (no all)
`gradient`: lower-right corner of target area (no all)
`highgradient`: same
`gradient_sized`, `highgradient_sized`: area of target (no all)
`mergedown`: upper-left of source, length of source, and...i'm unsure what purpose `dsty`/`dstx` serve honestly
`erase_region`: upper left and geom (0 all)
`from_plane`: upper left and geom (no all)
`render`: n/a
`as_rgba`: upper left and geometry (-1 all)
`visual_blit`: upper left and geom (0 all)

well, that's pretty fucking messy! glad we looked through this.
realizing that `visual_blit` is also involved here locks in 0 as full length, given that we don't want to invalidate every default-initialized `ncvisual_options` in existence.
relevant functions: `ncplane_resize()`, `ncplane_resize_simple()`, `ncplane_contents()`, `ncplane_hline_interp()`, `ncplane_vline_interp()`, `ncplane_box()`, `ncplane_gradient()`, `ncplane_highgradient()`, `ncplane_gradient_sized()`, `ncplane_highgradient_sized()`, `ncplane_mergedown()`, `ncplane_erase_region()`, `ncvisual_from_plane()`, `ncvisual_render()`, `ncplane_as_rgba()`, and their direct mode relatives.
Here's another question: `keepy` and `keepx` (wrt `ncplane_resize()`) must be within the old plane, but `keeplenx` and `keepleny` only need be within the new one. why wouldn't `keepleny + keepy` and `keeplenx + keepx` need to be within the old plane, as well? this leads to output like:

 ```
ncplane_resize_internal:742:70x80 @ 0/0 → 70/80 @ 0/0 (keeping 70x80 from 0/0)
ncplane_resize_internal:742:14x80 @ 0/0 → 14/80 @ 0/0 (keeping 14x80 from 0/0)
```

which looks wrong and jarring (how can i keep 70x80 from 0x0?), even if it's not actually leading to a false copy.
> ```
> ncplane_resize_internal:742:70x80 @ 0/0 → 70/80 @ 0/0 (keeping 70x80 from 0/0)
> ncplane_resize_internal:742:14x80 @ 0/0 → 14/80 @ 0/0 (keeping 14x80 from 0/0)
> ```
> which looks wrong and jarring (how can i keep 70x80 from 0x0?), even if it's not actually leading to a false copy.

replacing " from " with "@" clears up a lot of the confusion here
`resize`: upper-left corner of area to keep, geom to keep, upper-left corner of kept target area, geom to target (no all)
`resize_simple`: geom to target (no all)
`contents`: upper-left corner of area to return, geom of area (-1 for all)
`hline`: length of line (no all)
`vline`: length of line (no all)
`box`: lower-right corner of area to boxify (no all)
`gradient`: lower-right corner of target area (no all)
`highgradient`: same
`gradient_sized`, `highgradient_sized`: area of target (no all)
`mergedown`: upper-left of source, length of source, and...i'm unsure what purpose `dsty`/`dstx` serve honestly
`erase_region`: upper left and geom (0 all)
`from_plane`: upper left and geom (no all)
`render`: n/a
`as_rgba`: upper left and geometry (-1 all)
`visual_blit`: upper left and geom (0 all)

well, that's pretty fucking messy! glad we looked through this.
realizing that `visual_blit` is also involved here locks in 0 as full length, given that we don't want to invalidate every default-initialized `ncvisual_options` in existence.
relevant functions: `ncplane_resize()`, `ncplane_resize_simple()`, `ncplane_contents()`, `ncplane_hline_interp()`, `ncplane_vline_interp()`, `ncplane_box()`, `ncplane_gradient()`, `ncplane_highgradient()`, `ncplane_gradient_sized()`, `ncplane_highgradient_sized()`, `ncplane_mergedown()`, `ncplane_erase_region()`, `ncvisual_from_plane()`, `ncvisual_render()`, `ncplane_as_rgba()`, and their direct mode relatives.
Here's another question: `keepy` and `keepx` (wrt `ncplane_resize()`) must be within the old plane, but `keeplenx` and `keepleny` only need be within the new one. why wouldn't `keepleny + keepy` and `keeplenx + keepx` need to be within the old plane, as well? this leads to output like:

 ```
ncplane_resize_internal:742:70x80 @ 0/0 → 70/80 @ 0/0 (keeping 70x80 from 0/0)
ncplane_resize_internal:742:14x80 @ 0/0 → 14/80 @ 0/0 (keeping 14x80 from 0/0)
```

which looks wrong and jarring (how can i keep 70x80 from 0x0?), even if it's not actually leading to a false copy.
> ```
> ncplane_resize_internal:742:70x80 @ 0/0 → 70/80 @ 0/0 (keeping 70x80 from 0/0)
> ncplane_resize_internal:742:14x80 @ 0/0 → 14/80 @ 0/0 (keeping 14x80 from 0/0)
> ```
> which looks wrong and jarring (how can i keep 70x80 from 0x0?), even if it's not actually leading to a false copy.

replacing " from " with "@" clears up a lot of the confusion here
`resize`: upper-left corner of area to keep, geom to keep, upper-left corner of kept target area, geom to target (no all)
`resize_simple`: geom to target (no all)
`contents`: upper-left corner of area to return, geom of area (-1 for all)
`hline`: length of line (no all)
`vline`: length of line (no all)
`box`: lower-right corner of area to boxify (no all)
`gradient`: lower-right corner of target area (no all)
`highgradient`: same
`gradient_sized`, `highgradient_sized`: area of target (no all)
`mergedown`: upper-left of source, length of source, and...i'm unsure what purpose `dsty`/`dstx` serve honestly
`erase_region`: upper left and geom (0 all)
`from_plane`: upper left and geom (no all)
`render`: n/a
`as_rgba`: upper left and geometry (-1 all)
`visual_blit`: upper left and geom (0 all)

well, that's pretty fucking messy! glad we looked through this.
realizing that `visual_blit` is also involved here locks in 0 as full length, given that we don't want to invalidate every default-initialized `ncvisual_options` in existence.
relevant functions: `ncplane_resize()`, `ncplane_resize_simple()`, `ncplane_contents()`, `ncplane_hline_interp()`, `ncplane_vline_interp()`, `ncplane_box()`, `ncplane_gradient()`, `ncplane_highgradient()`, `ncplane_gradient_sized()`, `ncplane_highgradient_sized()`, `ncplane_mergedown()`, `ncplane_erase_region()`, `ncvisual_from_plane()`, `ncvisual_render()`, `ncplane_as_rgba()`, and their direct mode relatives.
Here's another question: `keepy` and `keepx` (wrt `ncplane_resize()`) must be within the old plane, but `keeplenx` and `keepleny` only need be within the new one. why wouldn't `keepleny + keepy` and `keeplenx + keepx` need to be within the old plane, as well? this leads to output like:

 ```
ncplane_resize_internal:742:70x80 @ 0/0 → 70/80 @ 0/0 (keeping 70x80 from 0/0)
ncplane_resize_internal:742:14x80 @ 0/0 → 14/80 @ 0/0 (keeping 14x80 from 0/0)
```

which looks wrong and jarring (how can i keep 70x80 from 0x0?), even if it's not actually leading to a false copy.
> ```
> ncplane_resize_internal:742:70x80 @ 0/0 → 70/80 @ 0/0 (keeping 70x80 from 0/0)
> ncplane_resize_internal:742:14x80 @ 0/0 → 14/80 @ 0/0 (keeping 14x80 from 0/0)
> ```
> which looks wrong and jarring (how can i keep 70x80 from 0x0?), even if it's not actually leading to a false copy.

replacing " from " with "@" clears up a lot of the confusion here
`resize`: upper-left corner of area to keep, geom to keep, upper-left corner of kept target area, geom to target (no all)
`resize_simple`: geom to target (no all)
`contents`: upper-left corner of area to return, geom of area (-1 for all)
`hline`: length of line (no all)
`vline`: length of line (no all)
`box`: lower-right corner of area to boxify (no all)
`gradient`: lower-right corner of target area (no all)
`highgradient`: same
`gradient_sized`, `highgradient_sized`: area of target (no all)
`mergedown`: upper-left of source, length of source, and...i'm unsure what purpose `dsty`/`dstx` serve honestly
`erase_region`: upper left and geom (0 all)
`from_plane`: upper left and geom (no all)
`render`: n/a
`as_rgba`: upper left and geometry (-1 all)
`visual_blit`: upper left and geom (0 all)

well, that's pretty fucking messy! glad we looked through this.
realizing that `visual_blit` is also involved here locks in 0 as full length, given that we don't want to invalidate every default-initialized `ncvisual_options` in existence.

Fix patch:
diff --git a/NEWS.md b/NEWS.md
index 20e9083a32..be59704ae6 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -12,6 +12,14 @@ rearrangements of Notcurses.
     deprecated functionality, ABI3 ought require small changes, if any.
 
 * 2.4.9 (not yet released)
+  * The handling of geometry and distance has been normalized across all
+    functions. Lengths are now `unsigned` as opposed to `int`. Where -1 was
+    being used to indicate "everything", 0 is now required. This affects
+    `ncplane_as_rgba()`, `ncplane_contents()`, and `ncvisual_from_plane()`,
+    which all used -1. A length of zero passed to line-drawing functions is
+    now an error. Several line-drawing functions now reliably return errors
+    as opposed to short successes. Dimensions of 0 to `ncplane_mergedown()`
+    now mean "everything".
   * `ncvisual_geom()` has been introduced, using the `ncvgeom` struct
     introduced for direct mode. This allows complete statement of geometry
     for an `ncvisual`. It replaces `ncvisual_blitter_geom()`, which has been
diff --git a/USAGE.md b/USAGE.md
index 063b96a4ff..b60a813ba3 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -526,10 +526,11 @@ int ncdirect_putegc(struct ncdirect* nc, uint64_t channels,
 // horizontal line, |len| cannot exceed the screen width minus the cursor's
 // offset. For a vertical line, it may be as long as you'd like; the screen
 // will scroll as necessary. All lines start at the current cursor position.
-int ncdirect_hline_interp(struct ncdirect* n, const char* egc, int len,
-                          uint64_t h1, uint64_t h2);
-int ncdirect_vline_interp(struct ncdirect* n, const char* egc, int len,
-                          uint64_t h1, uint64_t h2);
+// A length of 0 is an error, resulting in a return of -1.
+int ncdirect_hline_interp(struct ncdirect* n, const char* egc,
+                          unsigned len, uint64_t h1, uint64_t h2);
+int ncdirect_vline_interp(struct ncdirect* n, const char* egc,
+                          unsigned len, uint64_t h1, uint64_t h2);
 
 // Draw a box with its upper-left corner at the current cursor position, having
 // dimensions |ylen|x|xlen|. See ncplane_box() for more information. The
@@ -917,12 +918,18 @@ struct ncplane* ncplane_dup(struct ncplane* n, void* opaque);
 
 // Merge the ncplane 'src' down onto the ncplane 'dst'. This is most rigorously
 // defined as "write to 'dst' the frame that would be rendered were the entire
-// stack made up only of 'src' and, below it, 'dst', and 'dst' was the entire
-// rendering region." Merging is independent of the position of 'src' viz 'dst'
-// on the z-axis. If 'src' does not intersect with 'dst', 'dst' will not be
-// changed, but it is not an error. The source plane still exists following
-// this operation. Do not supply the same plane for both 'src' and 'dst'.
-int ncplane_mergedown(struct ncplane* restrict src, struct ncplane* restrict dst);
+// stack made up only of the specified subregion of 'src' and, below it, the
+// subregion of 'dst' having the specified origin. Merging is independent of
+// the position of 'src' viz 'dst' on the z-axis. It is an error to define a
+// subregion that is not entirely contained within 'src'. It is an error to
+// define a target origin such that the projected subregion is not entirely
+// contained within 'dst'.  Behavior is undefined if 'src' and 'dst' are
+// equivalent. 'dst' is modified, but 'src' remains unchanged. Neither 'src'
+// nor 'dst' may have sprixels. Lengths of 0 mean "everything left".
+int ncplane_mergedown(struct ncplane* restrict src, struct ncplane* restrict dst,
+                      unsigned begsrcy, unsigned begsrcx,
+                      unsigned leny, unsigned lenx,
+                      unsigned dsty, unsigned dstx);
 
 // Merge the entirety of 'src' down onto the ncplane 'dst'. If 'src' does not
 // intersect with 'dst', 'dst' will not be changed, but it is not an error.
@@ -1214,16 +1221,19 @@ int ncplane_at_yx_cell(struct ncplane* n, int y, int x, nccell* c);
 // Create an RGBA flat array from the selected region of the ncplane 'nc'.
 // Start at the plane's 'begy'x'begx' coordinate (which must lie on the
 // plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and
-// 'lenx' can be specified as -1 to go through the boundary of the plane.
+// 'lenx' can be specified as 0 to go through the boundary of the plane.
 // Only glyphs from the specified blitset may be present. If 'pxdimy' and/or
 // 'pxdimx' are non-NULL, they will be filled in with the pixel geometry.
 uint32_t* ncplane_as_rgba(const struct ncplane* n, ncblitter_e blit,
-                          int begy, int begx, int leny, int lenx,
-                          int* pxdimy, int* pxdimx);
+                          unsigned begy, unsigned begx, unsigned leny,
+                          unsigned lenx, unsigned* pxdimy, unsigned* pxdimx);
 
-// return a nul-terminated, heap copy of the current (UTF-8) contents.
-char* ncplane_contents(const struct ncplane* nc, int begy, int begx,
-                           int leny, int lenx);
+// Create a flat string from the EGCs of the selected region of the ncplane
+// 'n'. Start at the plane's 'begy'x'begx' coordinate (which must lie on the
+// plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and
+// 'lenx' can be specified as 0 to go through the boundary of the plane.
+char* ncplane_contents(const struct ncplane* nc, unsigned begy, unsigned begx,
+                       unsigned leny, unsigned lenx);
 
 // Manipulate the opaque user pointer associated with this plane.
 // ncplane_set_userptr() returns the previous userptr after replacing
@@ -1496,21 +1506,20 @@ on both sides. Boxes allow fairly detailed specification of how they're drawn.
 // current cursor position. The cursor will end at the cell following the last
 // cell output (even, perhaps counter-intuitively, when drawing vertical
 // lines), just as if ncplane_putc() was called at that spot. Return the
-// number of cells drawn on success. On error, return the negative number of
-// cells drawn.
-int ncplane_hline_interp(struct ncplane* n, const nccell* c, int len,
-                         uint64_t c1, uint64_t c2);
+// number of cells drawn on success. A length of 0 is an error.
+int ncplane_hline_interp(struct ncplane* n, const nccell* c,
+                         unsigned len, uint64_t c1, uint64_t c2);
 
 static inline int
-ncplane_hline(struct ncplane* n, const nccell* c, int len){
+ncplane_hline(struct ncplane* n, const nccell* c, unsigned len){
   return ncplane_hline_interp(n, c, len, c->channels, c->channels);
 }
 
-int ncplane_vline_interp(struct ncplane* n, const nccell* c, int len,
-                         uint64_t c1, uint64_t c2);
+int ncplane_vline_interp(struct ncplane* n, const nccell* c,
+                         unsigned len, uint64_t c1, uint64_t c2);
 
 static inline int
-ncplane_vline(struct ncplane* n, const nccell* c, int len){
+ncplane_vline(struct ncplane* n, const nccell* c, unsigned len){
   return ncplane_vline_interp(n, c, len, c->channels, c->channels);
 }
 
@@ -3291,8 +3300,11 @@ struct ncvisual* ncvisual_from_palidx(const void* data, int rows,
 // glyph will result in a NULL being returned. This function exists so that
 // planes can be subjected to ncvisual transformations. If possible, it's
 // better to create the ncvisual from memory using ncvisual_from_rgba().
-struct ncvisual* ncvisual_from_plane(const struct ncplane* n, ncblitter_e blit,
-                                     int begy, int begx, int leny, int lenx);
+// Lengths of 0 are interpreted to mean "all available remaining area".
+struct ncvisual* ncvisual_from_plane(const struct ncplane* n,
+                                     ncblitter_e blit,
+                                     unsigned begy, unsigned begx,
+                                     unsigned leny, unsigned lenx);
 ```
 
 Various transformations can be applied to an `ncvisual`, regardless of how
diff --git a/doc/man/man3/notcurses_direct.3.md b/doc/man/man3/notcurses_direct.3.md
index d98f765f13..4332d93324 100644
--- a/doc/man/man3/notcurses_direct.3.md
+++ b/doc/man/man3/notcurses_direct.3.md
@@ -78,9 +78,9 @@ notcurses_direct - minimal notcurses instances for styling text
 
 **const char* ncdirect_detected_terminal(const struct ncdirect* ***n***);**
 
-**int ncdirect_hline_interp(struct ncdirect* ***n***, const char* ***egc***, int ***len***, uint64_t ***h1***, uint64_t ***h2***);**
+**int ncdirect_hline_interp(struct ncdirect* ***n***, const char* ***egc***, unsigned ***len***, uint64_t ***h1***, uint64_t ***h2***);**
 
-**int ncdirect_vline_interp(struct ncdirect* ***n***, const char* ***egc***, int ***len***, uint64_t ***h1***, uint64_t ***h2***);**
+**int ncdirect_vline_interp(struct ncdirect* ***n***, const char* ***egc***, unsigned ***len***, uint64_t ***h1***, uint64_t ***h2***);**
 
 **int ncdirect_box(struct ncdirect* ***n***, uint64_t ***ul***, uint64_t ***ur***, uint64_t ***ll***, uint64_t ***lr***, const wchar_t* ***wchars***, int ***ylen***, int ***xlen***, unsigned ***ctlword***);**
 
@@ -232,8 +232,11 @@ space as is necessary". It is an error to pass a negative number for either.
 points to a valid **struct ncdirect**, which can be used until it is provided
 to **ncdirect_stop**.
 
-**ncdirect_putstr** and **ncdirect_printf_aligned** return the number of bytes
-written on success. On failure, they return some negative number.
+**ncdirect_printf_aligned** returns the number of bytes written on success. On
+failure, it returns some negative number.
+
+**ncdirect_putstr** returns a nonnegative number on success, and **EOF**
+on failure.
 
 **ncdirect_putegc** returns the number of columns consumed on success, or -1
 on failure. If ***sbytes*** is not **NULL**, the number of bytes consumed
diff --git a/doc/man/man3/notcurses_lines.3.md b/doc/man/man3/notcurses_lines.3.md
index ba500201d9..8eeca69b67 100644
--- a/doc/man/man3/notcurses_lines.3.md
+++ b/doc/man/man3/notcurses_lines.3.md
@@ -10,13 +10,13 @@ notcurses_lines - operations on lines and boxes
 
 **#include <notcurses/notcurses.h>**
 
-**int ncplane_hline_interp(struct ncplane* ***n***, const nccell* ***c***, int ***len***, uint64_t ***c1***, uint64_t ***c2***);**
+**int ncplane_hline_interp(struct ncplane* ***n***, const nccell* ***c***, unsigned ***len***, uint64_t ***c1***, uint64_t ***c2***);**
 
-**static inline int ncplane_hline(struct ncplane* ***n***, const nccell* ***c***, int ***len***);**
+**static inline int ncplane_hline(struct ncplane* ***n***, const nccell* ***c***, unsigned ***len***);**
 
-**int ncplane_vline_interp(struct ncplane* ***n***, const nccell* ***c***, int ***len***, uint64_t ***c1***, uint64_t ***c2***);**
+**int ncplane_vline_interp(struct ncplane* ***n***, const nccell* ***c***, unsigned ***len***, uint64_t ***c1***, uint64_t ***c2***);**
 
-**static inline int ncplane_vline(struct ncplane* ***n***, const nccell* ***c***, int ***len***);**
+**static inline int ncplane_vline(struct ncplane* ***n***, const nccell* ***c***, unsigned ***len***);**
 
 ```c
 #define NCBOXMASK_TOP    0x0001
@@ -78,6 +78,10 @@ Box- and line-drawing is unaffected by a plane's scrolling status.
 **ncplane_format** returns -1 if either **ystop** or **xstop** is less than the
 current equivalent position, otherwise 0.
 
+**ncplane_hline_interp**, **ncplane_hline**, **ncplane_vline_interp**, and
+**ncplane_vline** all return the number of glyphs drawn on success, or -1
+on failure. Passing a length of 0 is an error.
+
 # SEE ALSO
 
 **notcurses(3)**,
diff --git a/doc/man/man3/notcurses_plane.3.md b/doc/man/man3/notcurses_plane.3.md
index cd1a2a1808..6cdf71f7b6 100644
--- a/doc/man/man3/notcurses_plane.3.md
+++ b/doc/man/man3/notcurses_plane.3.md
@@ -123,9 +123,9 @@ typedef struct ncplane_options {
 
 **int ncplane_at_yx_cell(struct ncplane* ***n***, int ***y***, int ***x***, nccell* ***c***);**
 
-**uint32_t* ncplane_as_rgba(const struct ncplane* ***nc***, ncblitter_e ***blit***, int ***begy***, int ***begx***, int ***leny***, int ***lenx***, int* ***pxdimy***, int* ***pxdimx***);**
+**uint32_t* ncplane_as_rgba(const struct ncplane* ***nc***, ncblitter_e ***blit***, unsigned ***begy***, unsigned ***begx***, unsigned ***leny***, unsigned ***lenx***, unsigned* ***pxdimy***, unsigned* ***pxdimx***);**
 
-**char* ncplane_contents(const struct ncplane* ***nc***, int ***begy***, int ***begx***, int ***leny***, int ***lenx***);**
+**char* ncplane_contents(const struct ncplane* ***nc***, unsigned ***begy***, unsigned ***begx***, unsigned ***leny***, unsigned ***lenx***);**
 
 **void* ncplane_set_userptr(struct ncplane* ***n***, void* ***opaque***);**
 
@@ -205,7 +205,7 @@ typedef struct ncplane_options {
 
 **void notcurses_drop_planes(struct notcurses* ***nc***);**
 
-**int ncplane_mergedown(struct ncplane* ***src***, struct ncplane* ***dst***, int ***begsrcy***, int ***begsrcx***, int ***leny***, int ***lenx***, int ***dsty***, int ***dstx***);**
+**int ncplane_mergedown(struct ncplane* ***src***, struct ncplane* ***dst***, unsigned ***begsrcy***, unsigned ***begsrcx***, unsigned ***leny***, unsigned ***lenx***, unsigned ***dsty***, unsigned ***dstx***);**
 
 **int ncplane_mergedown_simple(struct ncplane* restrict ***src***, struct ncplane* restrict ***dst***);**
 
diff --git a/doc/man/man3/notcurses_visual.3.md b/doc/man/man3/notcurses_visual.3.md
index 424def9185..20ddc6f429 100644
--- a/doc/man/man3/notcurses_visual.3.md
+++ b/doc/man/man3/notcurses_visual.3.md
@@ -77,7 +77,7 @@ typedef struct ncvgeom {
 
 **struct ncvisual* ncvisual_from_palidx(const void* ***data***, int ***rows***, int ***rowstride***, int ***cols***, int ***palsize***, int ***pstride***, const uint32_t* ***palette***);**
 
-**struct ncvisual* ncvisual_from_plane(struct ncplane* ***n***, ncblitter_e ***blit***, int ***begy***, int ***begx***, int ***leny***, int ***lenx***);**
+**struct ncvisual* ncvisual_from_plane(struct ncplane* ***n***, ncblitter_e ***blit***, unsigned ***begy***, unsigned ***begx***, unsigned ***leny***, unsigned ***lenx***);**
 
 **int ncvisual_geom(const struct notcurses* ***nc***, const struct ncvisual* ***n***, const struct ncvisual_options* ***vopts***, ncvgeom* ***geom***);**
 
diff --git a/include/ncpp/Direct.hh b/include/ncpp/Direct.hh
index 28a2b64d39..cad03b27b6 100644
--- a/include/ncpp/Direct.hh
+++ b/include/ncpp/Direct.hh
@@ -177,12 +177,12 @@ namespace ncpp
 
 		// TODO: ncdirect_printf_aligned (will need a version which takes vargs)
 
-		int hline_interp (const char* egc, int len, uint64_t h1, uint64_t h2) const noexcept
+		int hline_interp (const char* egc, unsigned len, uint64_t h1, uint64_t h2) const noexcept
 		{
 			return ncdirect_hline_interp (direct, egc, len, h1, h2);
 		}
 
-		int vline_interp (const char* egc, int len, uint64_t h1, uint64_t h2) const noexcept
+		int vline_interp (const char* egc, unsigned len, uint64_t h1, uint64_t h2) const noexcept
 		{
 			return ncdirect_vline_interp (direct, egc, len, h1, h2);
 		}
diff --git a/include/ncpp/Plane.hh b/include/ncpp/Plane.hh
index 7fcad4832e..80bd1f9aa5 100644
--- a/include/ncpp/Plane.hh
+++ b/include/ncpp/Plane.hh
@@ -452,17 +452,17 @@ namespace ncpp
 			return move_above (*above);
 		}
 
-		bool mergedown (Plane &dst, int begsrcy, int begsrcx, int leny, int lenx, int dsty, int dstx) const
+		bool mergedown (Plane &dst, unsigned begsrcy, unsigned begsrcx, unsigned leny, unsigned lenx, unsigned dsty, unsigned dstx) const
 		{
 			return mergedown (&dst, begsrcy, begsrcx, leny, lenx, dsty, dstx);
 		}
 
-		bool mergedown (Plane *dst, int begsrcy, int begsrcx, int leny, int lenx, int dsty, int dstx) const
+		bool mergedown (Plane *dst, unsigned begsrcy, unsigned begsrcx, unsigned leny, unsigned lenx, unsigned dsty, unsigned dstx) const
 		{
-			if (dst != nullptr && plane == dst->plane)
+			if (plane == dst->plane)
 				throw invalid_argument ("'dst' must refer to a different plane than the one this method is called on");
 
-			return error_guard (ncplane_mergedown (plane, dst != nullptr ? dst->plane : nullptr, begsrcy, begsrcx, leny, lenx, dsty, dstx), -1);
+			return error_guard (ncplane_mergedown (plane, dst->plane, begsrcy, begsrcx, leny, lenx, dsty, dstx), -1);
 		}
 
 		bool mergedown_simple (Plane &dst) const
@@ -472,7 +472,7 @@ namespace ncpp
 
 		bool mergedown_simple (Plane *dst) const
 		{
-			if (dst == nullptr || plane == dst->plane)
+			if (plane == dst->plane)
 				throw invalid_argument ("'dst' must refer to a different plane than the one this method is called on");
 
 			return error_guard (ncplane_mergedown_simple (plane, dst->plane), -1);
@@ -699,22 +699,22 @@ namespace ncpp
 			return error_guard<int> (ncplane_vprintf_aligned (plane, y, static_cast<ncalign_e>(align), format, ap), -1);
 		}
 
-		int hline (const Cell &c, int len) const NOEXCEPT_MAYBE
+		int hline (const Cell &c, unsigned len) const NOEXCEPT_MAYBE
 		{
 			return error_guard<int> (ncplane_hline (plane, c, len), -1);
 		}
 
-		int hline (const Cell &c, int len, uint64_t c1, uint64_t c2) const NOEXCEPT_MAYBE
+		int hline (const Cell &c, unsigned len, uint64_t c1, uint64_t c2) const NOEXCEPT_MAYBE
 		{
 			return error_guard<int> (ncplane_hline_interp (plane, c, len, c1, c2), -1);
 		}
 
-		int vline (const Cell &c, int len) const NOEXCEPT_MAYBE
+		int vline (const Cell &c, unsigned len) const NOEXCEPT_MAYBE
 		{
 			return error_guard<int> (ncplane_vline (plane, c, len), -1);
 		}
 
-		int vline (const Cell &c, int len, uint64_t c1, uint64_t c2) const NOEXCEPT_MAYBE
+		int vline (const Cell &c, unsigned len, uint64_t c1, uint64_t c2) const NOEXCEPT_MAYBE
 		{
 			return error_guard<int> (ncplane_vline_interp (plane, c, len, c1, c2), -1);
 		}
@@ -803,12 +803,12 @@ namespace ncpp
 			return error_guard<int> (ncplane_polyfill_yx (plane, y, x, c), -1);
 		}
 
-		uint32_t* rgba(ncblitter_e blit, int begy, int begx, int leny, int lenx) const noexcept
+		uint32_t* rgba(ncblitter_e blit, unsigned begy, unsigned begx, unsigned leny, unsigned lenx) const noexcept
 		{
 			return ncplane_as_rgba (plane, blit, begy, begx, leny, lenx, nullptr, nullptr);
 		}
 
-		char* content(int begy, int begx, int leny, int lenx) const noexcept
+		char* content(unsigned begy, unsigned begx, unsigned leny, unsigned lenx) const noexcept
 		{
 			return ncplane_contents (plane, begy, begx, leny, lenx);
 		}
diff --git a/include/ncpp/Visual.hh b/include/ncpp/Visual.hh
index 2f72fd87f8..115c95e088 100644
--- a/include/ncpp/Visual.hh
+++ b/include/ncpp/Visual.hh
@@ -32,7 +32,7 @@ namespace ncpp
 				throw init_error ("Notcurses failed to create a new visual");
 		}
 
-		explicit Visual (const Plane& p, ncblitter_e blit, int begy, int begx, int leny, int lenx)
+		explicit Visual (const Plane& p, ncblitter_e blit, unsigned begy, unsigned begx, unsigned leny, unsigned lenx)
 			: Root (NotCurses::get_instance ())
 		{
 			visual = ncvisual_from_plane (p, blit, begy, begx, leny, lenx);
diff --git a/include/notcurses/direct.h b/include/notcurses/direct.h
index c3766d7c9c..e416661872 100644
--- a/include/notcurses/direct.h
+++ b/include/notcurses/direct.h
@@ -116,7 +116,7 @@ API unsigned ncdirect_palette_size(const struct ncdirect* nc)
 
 // Output the string |utf8| according to the channels |channels|. Note that
 // ncdirect_putstr() does not explicitly flush output buffers, so it will not
-// necessarily be immediately visible.
+// necessarily be immediately visible. Returns EOF on error.
 API int ncdirect_putstr(struct ncdirect* nc, uint64_t channels, const char* utf8)
   __attribute__ ((nonnull (1, 3)));
 
@@ -263,13 +263,13 @@ API const nccapabilities* ncdirect_capabilities(const struct ncdirect* n)
 // horizontal line, |len| cannot exceed the screen width minus the cursor's
 // offset. For a vertical line, it may be as long as you'd like; the screen
 // will scroll as necessary. All lines start at the current cursor position.
-API int ncdirect_hline_interp(struct ncdirect* n, const char* egc, int len,
-                              uint64_t h1, uint64_t h2)
-  __attribute__ ((nonnull (1)));
+API int ncdirect_hline_interp(struct ncdirect* n, const char* egc,
+                              unsigned len, uint64_t h1, uint64_t h2)
+  __attribute__ ((nonnull (1, 2)));
 
-API int ncdirect_vline_interp(struct ncdirect* n, const char* egc, int len,
-                              uint64_t h1, uint64_t h2)
-  __attribute__ ((nonnull (1)));
+API int ncdirect_vline_interp(struct ncdirect* n, const char* egc,
+                              unsigned len, uint64_t h1, uint64_t h2)
+  __attribute__ ((nonnull (1, 2)));
 
 // Draw a box with its upper-left corner at the current cursor position, having
 // dimensions |ylen|x|xlen|. See ncplane_box() for more information. The
@@ -277,40 +277,40 @@ API int ncdirect_vline_interp(struct ncdirect* n, const char* egc, int len,
 // array of 6 wide characters: UL, UR, LL, LR, HL, VL.
 API int ncdirect_box(struct ncdirect* n, uint64_t ul, uint64_t ur,
                      uint64_t ll, uint64_t lr, const wchar_t* wchars,
-                     int ylen, int xlen, unsigned ctlword)
-  __attribute__ ((nonnull (1)));
+                     unsigned ylen, unsigned xlen, unsigned ctlword)
+  __attribute__ ((nonnull (1, 6)));
 
 __attribute__ ((nonnull (1))) static inline int
 ncdirect_light_box(struct ncdirect* n, uint64_t ul, uint64_t ur,
                    uint64_t ll, uint64_t lr,
-                   int ylen, int xlen, unsigned ctlword){
+                   unsigned ylen, unsigned xlen, unsigned ctlword){
   return ncdirect_box(n, ul, ur, ll, lr, NCBOXLIGHTW, ylen, xlen, ctlword);
 }
 
 __attribute__ ((nonnull (1))) static inline int
 ncdirect_heavy_box(struct ncdirect* n, uint64_t ul, uint64_t ur,
                    uint64_t ll, uint64_t lr,
-                   int ylen, int xlen, unsigned ctlword){
+                   unsigned ylen, unsigned xlen, unsigned ctlword){
   return ncdirect_box(n, ul, ur, ll, lr, NCBOXHEAVYW, ylen, xlen, ctlword);
 }
 
 __attribute__ ((nonnull (1))) static inline int
 ncdirect_ascii_box(struct ncdirect* n, uint64_t ul, uint64_t ur,
                    uint64_t ll, uint64_t lr,
-                   int ylen, int xlen, unsigned ctlword){
+                   unsigned ylen, unsigned xlen, unsigned ctlword){
   return ncdirect_box(n, ul, ur, ll, lr, NCBOXASCIIW, ylen, xlen, ctlword);
 }
 
 // ncdirect_box() with the rounded box-drawing characters
 API int ncdirect_rounded_box(struct ncdirect* n, uint64_t ul, uint64_t ur,
                              uint64_t ll, uint64_t lr,
-                             int ylen, int xlen, unsigned ctlword)
+                             unsigned ylen, unsigned xlen, unsigned ctlword)
   __attribute__ ((nonnull (1)));
 
 // ncdirect_box() with the double box-drawing characters
 API int ncdirect_double_box(struct ncdirect* n, uint64_t ul, uint64_t ur,
                             uint64_t ll, uint64_t lr,
-                            int ylen, int xlen, unsigned ctlword)
+                            unsigned ylen, unsigned xlen, unsigned ctlword)
   __attribute__ ((nonnull (1)));
 
 // Provide a NULL 'ts' to block at length, a 'ts' of 0 for non-blocking
diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h
index b45bbd66f7..5a5ef598d5 100644
--- a/include/notcurses/notcurses.h
+++ b/include/notcurses/notcurses.h
@@ -1219,7 +1219,8 @@ notcurses_term_dim_yx(const struct notcurses* n, int* RESTRICT rows, int* RESTRI
 // or NULL on error. This EGC must be free()d by the caller. The stylemask and
 // channels are written to 'stylemask' and 'channels', respectively.
 API char* notcurses_at_yx(struct notcurses* nc, int yoff, int xoff,
-                          uint16_t* stylemask, uint64_t* channels);
+                          uint16_t* stylemask, uint64_t* channels)
+  __attribute__ ((nonnull (1)));
 
 // Horizontal alignment relative to the parent plane. Use ncalign_e for 'x'.
 #define NCPLANE_OPTION_HORALIGNED 0x0001ull
@@ -1574,20 +1575,19 @@ API void notcurses_stats_reset(struct notcurses* nc, ncstats* stats)
 //
 // Essentially, the kept material does not move. It serves to anchor the
 // resized plane. If there is no kept material, the plane can move freely.
-API int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny,
-                       int keeplenx, int yoff, int xoff, int ylen, int xlen);
+API int ncplane_resize(struct ncplane* n, int keepy, int keepx,
+                       unsigned keepleny, unsigned keeplenx,
+                       int yoff, int xoff,
+                       unsigned ylen, unsigned xlen);
 
 // Resize the plane, retaining what data we can (everything, unless we're
 // shrinking in some dimension). Keep the origin where it is.
 static inline int
-ncplane_resize_simple(struct ncplane* n, int ylen, int xlen){
-  if(ylen < 0 || xlen < 0){
-    return -1;
-  }
+ncplane_resize_simple(struct ncplane* n, unsigned ylen, unsigned xlen){
   int oldy, oldx;
   ncplane_dim_yx(n, &oldy, &oldx); // current dimensions of 'n'
-  int keepleny = oldy > ylen ? ylen : oldy;
-  int keeplenx = oldx > xlen ? xlen : oldx;
+  unsigned keepleny = (unsigned)oldy > ylen ? ylen : (unsigned)oldy;
+  unsigned keeplenx = (unsigned)oldx > xlen ? xlen : (unsigned)oldx;
   return ncplane_resize(n, 0, 0, keepleny, keeplenx, 0, 0, ylen, xlen);
 }
 
@@ -1763,9 +1763,9 @@ API int ncplane_at_yx_cell(struct ncplane* n, int y, int x, nccell* c);
 // Create a flat string from the EGCs of the selected region of the ncplane
 // 'n'. Start at the plane's 'begy'x'begx' coordinate (which must lie on the
 // plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and
-// 'lenx' can be specified as -1 to go through the boundary of the plane.
-API char* ncplane_contents(struct ncplane* n, int begy, int begx,
-                           int leny, int lenx);
+// 'lenx' can be specified as 0 to go through the boundary of the plane.
+API char* ncplane_contents(struct ncplane* n, unsigned begy, unsigned begx,
+                           unsigned leny, unsigned lenx);
 
 // Manipulate the opaque user pointer associated with this plane.
 // ncplane_set_userptr() returns the previous userptr after replacing
@@ -1783,12 +1783,13 @@ API void ncplane_center_abs(const struct ncplane* n, int* RESTRICT y,
 // Create an RGBA flat array from the selected region of the ncplane 'nc'.
 // Start at the plane's 'begy'x'begx' coordinate (which must lie on the
 // plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and
-// 'lenx' can be specified as -1 to go through the boundary of the plane.
+// 'lenx' can be specified as 0 to go through the boundary of the plane.
 // Only glyphs from the specified ncblitset may be present. If 'pxdimy' and/or
-// 'pxdimx' are non-NULL, they will be filled in with the pixel geometry.
+// 'pxdimx' are non-NULL, they will be filled in with the total pixel geometry.
 API ALLOC uint32_t* ncplane_as_rgba(const struct ncplane* n, ncblitter_e blit,
-                                    int begy, int begx, int leny, int lenx,
-                                    int* pxdimy, int* pxdimx)
+                                    unsigned begy, unsigned begx,
+                                    unsigned leny, unsigned lenx,
+                                    unsigned* pxdimy, unsigned* pxdimx)
   __attribute__ ((nonnull (1)));
 
 // Return the offset into 'availu' at which 'u' ought be output given the
@@ -2155,20 +2156,22 @@ API int ncplane_puttext(struct ncplane* n, int y, ncalign_e align,
 // cell output (even, perhaps counter-intuitively, when drawing vertical
 // lines), just as if ncplane_putc() was called at that spot. Return the
 // number of cells drawn on success. On error, return the negative number of
-// cells drawn.
-API int ncplane_hline_interp(struct ncplane* n, const nccell* c, int len,
-                             uint64_t c1, uint64_t c2);
+// cells drawn. A length of 0 is an error, resulting in a return of -1.
+API int ncplane_hline_interp(struct ncplane* n, const nccell* c,
+                             unsigned len, uint64_t c1, uint64_t c2)
+  __attribute__ ((nonnull (1, 2)));
 
-static inline int
-ncplane_hline(struct ncplane* n, const nccell* c, int len){
+__attribute__ ((nonnull (1, 2))) static inline int
+ncplane_hline(struct ncplane* n, const nccell* c, unsigned len){
   return ncplane_hline_interp(n, c, len, c->channels, c->channels);
 }
 
-API int ncplane_vline_interp(struct ncplane* n, const nccell* c, int len,
-                             uint64_t c1, uint64_t c2);
+API int ncplane_vline_interp(struct ncplane* n, const nccell* c,
+                             unsigned len, uint64_t c1, uint64_t c2)
+  __attribute__ ((nonnull (1, 2)));
 
-static inline int
-ncplane_vline(struct ncplane* n, const nccell* c, int len){
+__attribute__ ((nonnull (1, 2))) static inline int
+ncplane_vline(struct ncplane* n, const nccell* c, unsigned len){
   return ncplane_vline_interp(n, c, len, c->channels, c->channels);
 }
 
@@ -2203,16 +2206,17 @@ ncplane_vline(struct ncplane* n, const nccell* c, int len){
 // are never drawn (since at most 2 edges can touch a box's corner).
 API int ncplane_box(struct ncplane* n, const nccell* ul, const nccell* ur,
                     const nccell* ll, const nccell* lr, const nccell* hline,
-                    const nccell* vline, int ystop, int xstop,
+                    const nccell* vline, unsigned ystop, unsigned xstop,
                     unsigned ctlword);
 
 // Draw a box with its upper-left corner at the current cursor position, having
 // dimensions 'ylen'x'xlen'. See ncplane_box() for more information. The
-// minimum box size is 2x2, and it cannot be drawn off-screen.
+// minimum box size is 2x2, and it cannot be drawn off-plane.
 static inline int
 ncplane_box_sized(struct ncplane* n, const nccell* ul, const nccell* ur,
                   const nccell* ll, const nccell* lr, const nccell* hline,
-                  const nccell* vline, int ylen, int xlen, unsigned ctlword){
+                  const nccell* vline, unsigned ylen, unsigned xlen,
+                  unsigned ctlword){
   int y, x;
   ncplane_cursor_yx(n, &y, &x);
   return ncplane_box(n, ul, ur, ll, lr, hline, vline, y + ylen - 1,
@@ -2299,28 +2303,32 @@ API int ncplane_stain(struct ncplane* n, int ystop, int xstop, uint64_t ul,
 // Merge the entirety of 'src' down onto the ncplane 'dst'. If 'src' does not
 // intersect with 'dst', 'dst' will not be changed, but it is not an error.
 API int ncplane_mergedown_simple(struct ncplane* RESTRICT src,
-                                 struct ncplane* RESTRICT dst);
+                                 struct ncplane* RESTRICT dst)
+  __attribute__ ((nonnull (1, 2)));
 
 // Merge the ncplane 'src' down onto the ncplane 'dst'. This is most rigorously
 // defined as "write to 'dst' the frame that would be rendered were the entire
 // stack made up only of the specified subregion of 'src' and, below it, the
 // subregion of 'dst' having the specified origin. Merging is independent of
 // the position of 'src' viz 'dst' on the z-axis. It is an error to define a
-// subregion of zero area, or that is not entirely contained within 'src'. It
-// is an error to define a target origin such that the projected subregion is
-// not entirely contained within 'dst'.  Behavior is undefined if 'src' and
-// 'dst' are equivalent. 'dst' is modified, but 'src' remains unchanged.
-// neither 'src' nor 'dst' may have sprixels.
+// subregion that is not entirely contained within 'src'. It is an error to
+// define a target origin such that the projected subregion is not entirely
+// contained within 'dst'.  Behavior is undefined if 'src' and 'dst' are
+// equivalent. 'dst' is modified, but 'src' remains unchanged. Neither 'src'
+// nor 'dst' may have sprixels. Lengths of 0 mean "everything left".
 API int ncplane_mergedown(struct ncplane* RESTRICT src,
                           struct ncplane* RESTRICT dst,
-                          int begsrcy, int begsrcx, int leny, int lenx,
-                          int dsty, int dstx);
+                          unsigned begsrcy, unsigned begsrcx,
+                          unsigned leny, unsigned lenx,
+                          unsigned dsty, unsigned dstx)
+  __attribute__ ((nonnull (1, 2)));
 
 // Erase every cell in the ncplane (each cell is initialized to the null glyph
 // and the default channels/styles). All cells associated with this ncplane are
 // invalidated, and must not be used after the call, *excluding* the base cell.
 // The cursor is homed. The plane's active attributes are unaffected.
-API void ncplane_erase(struct ncplane* n);
+API void ncplane_erase(struct ncplane* n)
+  __attribute__ ((nonnull (1)));
 
 // Erase every cell in the region starting at {ystart, xstart} and having size
 // {|ylen|x|xlen|} for non-zero lengths. If ystart and/or xstart are -1, the current
@@ -2832,10 +2840,12 @@ API ALLOC struct ncvisual* ncvisual_from_palidx(const void* data, int rows,
 // glyph will result in a NULL being returned. This function exists so that
 // planes can be subjected to ncvisual transformations. If possible, it's
 // better to create the ncvisual from memory using ncvisual_from_rgba().
+// Lengths of 0 are interpreted to mean "all available remaining area".
 API ALLOC struct ncvisual* ncvisual_from_plane(const struct ncplane* n,
                                                ncblitter_e blit,
-                                               int begy, int begx,
-                                               int leny, int lenx);
+                                               unsigned begy, unsigned begx,
+                                               unsigned leny, unsigned lenx)
+  __attribute__ ((nonnull (1)));
 
 #define NCVISUAL_OPTION_NODEGRADE     0x0001ull // fail rather than degrade
 #define NCVISUAL_OPTION_BLEND         0x0002ull // use NCALPHA_BLEND with visual
diff --git a/python/notcurses/plane.c b/python/notcurses/plane.c
index 3e5c62b936..f34ac8ab20 100644
--- a/python/notcurses/plane.c
+++ b/python/notcurses/plane.c
@@ -199,7 +199,10 @@ NcPlane_set_scrolling(NcPlaneObject *self, PyObject *args)
 static PyObject *
 NcPlane_resize(NcPlaneObject *self, PyObject *args, PyObject *kwds)
 {
-    int keepy = 0, keepx = 0, keepleny = 0, keeplenx = 0, yoff = 0, xoff = 0, ylen = 0, xlen = 0;
+    int keepy = 0, keepx = 0;
+    unsigned keepleny = 0, keeplenx = 0;
+    int yoff = 0, xoff = 0;
+    unsigned ylen = 0, xlen = 0;
 
     char *keywords[] = {"keepy", "keepx",
                         "keepleny", "keeplenx",
@@ -207,7 +210,7 @@ NcPlane_resize(NcPlaneObject *self, PyObject *args, PyObject *kwds)
                         "ylen", "xlen",
                         NULL};
 
-    GNU_PY_CHECK_BOOL(PyArg_ParseTupleAndKeywords(args, kwds, "iiiiiiii", keywords,
+    GNU_PY_CHECK_BOOL(PyArg_ParseTupleAndKeywords(args, kwds, "iiIIiiII", keywords,
                                                   &keepy, &keepx,
                                                   &keepleny, &keeplenx,
                                                   &yoff, &xoff,
@@ -221,9 +224,9 @@ NcPlane_resize(NcPlaneObject *self, PyObject *args, PyObject *kwds)
 static PyObject *
 NcPlane_resize_simple(NcPlaneObject *self, PyObject *args)
 {
-    int ylen = 0, xlen = 0;
+    unsigned ylen = 0, xlen = 0;
 
-    GNU_PY_CHECK_BOOL(PyArg_ParseTuple(args, "ii", &ylen, &xlen));
+    GNU_PY_CHECK_BOOL(PyArg_ParseTuple(args, "II", &ylen, &xlen));
 
     CHECK_NOTCURSES(ncplane_resize_simple(self->ncplane_ptr, ylen, xlen));
 
@@ -474,11 +477,11 @@ NcPlane_at_yx_cell(NcPlaneObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
 static PyObject *
 NcPlane_contents(NcPlaneObject *self, PyObject *args, PyObject *kwds)
 {
-    int beg_y = 0, beg_x = 0, len_y = -1, len_x = -1;
+    unsigned beg_y = 0, beg_x = 0, len_y = 0, len_x = 0;
 
     char *keywords[] = {"begy", "begx", "leny", "lenx", NULL};
 
-    GNU_PY_CHECK_BOOL(PyArg_ParseTupleAndKeywords(args, kwds, "ii|ii", keywords,
+    GNU_PY_CHECK_BOOL(PyArg_ParseTupleAndKeywords(args, kwds, "II|II", keywords,
                                                   &beg_y, &beg_x,
                                                   &len_y, &len_x));
 
@@ -960,14 +963,7 @@ NcPlane_mergedown_simple(NcPlaneObject *self, PyObject *args)
 
     GNU_PY_CHECK_BOOL(PyArg_ParseTuple(args, "|O!", &NcPlane_Type, &dst_obj));
 
-    if (NULL != dst_obj)
-    {
-        CHECK_NOTCURSES(ncplane_mergedown_simple(self->ncplane_ptr, dst_obj->ncplane_ptr));
-    }
-    else
-    {
-        CHECK_NOTCURSES(ncplane_mergedown_simple(self->ncplane_ptr, NULL));
-    }
+    CHECK_NOTCURSES(ncplane_mergedown_simple(self->ncplane_ptr, dst_obj->ncplane_ptr));
 
     Py_RETURN_NONE;
 }
@@ -976,14 +972,14 @@ static PyObject *
 NcPlane_mergedown(NcPlaneObject *self, PyObject *args, PyObject *kwds)
 {
     NcPlaneObject *dst_obj = NULL;
-    int begsrcy = 0, begsrcx = 0, leny = 0, lenx = 0;
-    int dsty = 0, dstx = 0;
+    unsigned begsrcy = 0, begsrcx = 0, leny = 0, lenx = 0;
+    unsigned dsty = 0, dstx = 0;
 
     char *keywords[] = {"dst",
                         "begsrcy", "begsrcx", "leny", "lenx",
                         "dsty", "dstx",
                         NULL};
-    GNU_PY_CHECK_BOOL(PyArg_ParseTupleAndKeywords(args, kwds, "O!iiiiii", keywords,
+    GNU_PY_CHECK_BOOL(PyArg_ParseTupleAndKeywords(args, kwds, "O!IIIIII", keywords,
                                                   &NcPlane_Type, &dst_obj,
                                                   &begsrcy, &begsrcx, &leny, &lenx,
                                                   &dsty, &dstx));
diff --git a/src/lib/direct.c b/src/lib/direct.c
index 217772c311..409758244b 100644
--- a/src/lib/direct.c
+++ b/src/lib/direct.c
@@ -66,6 +66,7 @@ int ncdirect_putegc(ncdirect* nc, uint64_t channels, const char* utf8,
 
 int ncdirect_cursor_up(ncdirect* nc, int num){
   if(num < 0){
+    logerror("requested negative move %d\n", num);
     return -1;
   }
   if(num == 0){
@@ -80,6 +81,7 @@ int ncdirect_cursor_up(ncdirect* nc, int num){
 
 int ncdirect_cursor_left(ncdirect* nc, int num){
   if(num < 0){
+    logerror("requested negative move %d\n", num);
     return -1;
   }
   if(num == 0){
@@ -94,6 +96,7 @@ int ncdirect_cursor_left(ncdirect* nc, int num){
 
 int ncdirect_cursor_right(ncdirect* nc, int num){
   if(num < 0){
+    logerror("requested negative move %d\n", num);
     return -1;
   }
   if(num == 0){
@@ -112,6 +115,7 @@ int ncdirect_cursor_right(ncdirect* nc, int num){
 // necessary but performing no carriage return -- a pure line feed.
 int ncdirect_cursor_down(ncdirect* nc, int num){
   if(num < 0){
+    logerror("requested negative move %d\n", num);
     return -1;
   }
   if(num == 0){
@@ -1263,8 +1267,12 @@ int ncdirect_set_bg_default(ncdirect* nc){
   return 0;
 }
 
-int ncdirect_hline_interp(ncdirect* n, const char* egc, int len,
+int ncdirect_hline_interp(ncdirect* n, const char* egc, unsigned len,
                           uint64_t c1, uint64_t c2){
+  if(len == 0){
+    logerror("passed zero length\n");
+    return -1;
+  }
   unsigned ur, ug, ub;
   int r1, g1, b1, r2, g2, b2;
   int br1, bg1, bb1, br2, bg2, bb2;
@@ -1282,7 +1290,7 @@ int ncdirect_hline_interp(ncdirect* n, const char* egc, int len,
   int deltbr = br2 - br1;
   int deltbg = bg2 - bg1;
   int deltbb = bb2 - bb1;
-  int ret;
+  unsigned ret;
   bool fgdef = false, bgdef = false;
   if(ncchannels_fg_default_p(c1) && ncchannels_fg_default_p(c2)){
     if(ncdirect_set_fg_default(n)){
@@ -1297,12 +1305,12 @@ int ncdirect_hline_interp(ncdirect* n, const char* egc, int len,
     bgdef = true;
   }
   for(ret = 0 ; ret < len ; ++ret){
-    int r = (deltr * ret) / len + r1;
-    int g = (deltg * ret) / len + g1;
-    int b = (deltb * ret) / len + b1;
-    int br = (deltbr * ret) / len + br1;
-    int bg = (deltbg * ret) / len + bg1;
-    int bb = (deltbb * ret) / len + bb1;
+    int r = (deltr * (int)ret) / (int)len + r1;
+    int g = (deltg * (int)ret) / (int)len + g1;
+    int b = (deltb * (int)ret) / (int)len + b1;
+    int br = (deltbr * (int)ret) / (int)len + br1;
+    int bg = (deltbg * (int)ret) / (int)len + bg1;
+    int bb = (deltbb * (int)ret) / (int)len + bb1;
     if(!fgdef){
       ncdirect_set_fg_rgb8(n, r, g, b);
     }
@@ -1310,14 +1318,19 @@ int ncdirect_hline_interp(ncdirect* n, const char* egc, int len,
       ncdirect_set_bg_rgb8(n, br, bg, bb);
     }
     if(fprintf(n->ttyfp, "%s", egc) < 0){
-      break;
+      logerror("error emitting egc [%s]\n", egc);
+      return -1;
     }
   }
   return ret;
 }
 
-int ncdirect_vline_interp(ncdirect* n, const char* egc, int len,
+int ncdirect_vline_interp(ncdirect* n, const char* egc, unsigned len,
                           uint64_t c1, uint64_t c2){
+  if(len == 0){
+    logerror("passed zero length\n");
+    return -1;
+  }
   unsigned ur, ug, ub;
   int r1, g1, b1, r2, g2, b2;
   int br1, bg1, bb1, br2, bg2, bb2;
@@ -1329,13 +1342,13 @@ int ncdirect_vline_interp(ncdirect* n, const char* egc, int len,
   br1 = ur; bg1 = ug; bb1 = ub;
   ncchannels_bg_rgb8(c2, &ur, &ug, &ub);
   br2 = ur; bg2 = ug; bb2 = ub;
-  int deltr = (r2 - r1) / (len + 1);
-  int deltg = (g2 - g1) / (len + 1);
-  int deltb = (b2 - b1) / (len + 1);
-  int deltbr = (br2 - br1) / (len + 1);
-  int deltbg = (bg2 - bg1) / (len + 1);
-  int deltbb = (bb2 - bb1) / (len + 1);
-  int ret;
+  int deltr = (r2 - r1) / ((int)len + 1);
+  int deltg = (g2 - g1) / ((int)len + 1);
+  int deltb = (b2 - b1) / ((int)len + 1);
+  int deltbr = (br2 - br1) / ((int)len + 1);
+  int deltbg = (bg2 - bg1) / ((int)len + 1);
+  int deltbb = (bb2 - bb1) / ((int)len + 1);
+  unsigned ret;
   bool fgdef = false, bgdef = false;
   if(ncchannels_fg_default_p(c1) && ncchannels_fg_default_p(c2)){
     if(ncdirect_set_fg_default(n)){
@@ -1363,12 +1376,12 @@ int ncdirect_vline_interp(ncdirect* n, const char* egc, int len,
     if(!bgdef){
       ncchannels_set_bg_rgb8(&channels, br1, bg1, bb1);
     }
-    if(ncdirect_putstr(n, channels, egc) <= 0){
-      break;
+    if(ncdirect_putstr(n, channels, egc) == EOF){
+      return -1;
     }
     if(len - ret > 1){
       if(ncdirect_cursor_down(n, 1) || ncdirect_cursor_left(n, 1)){
-        break;
+        return -1;
       }
     }
   }
@@ -1379,7 +1392,7 @@ int ncdirect_vline_interp(ncdirect* n, const char* egc, int len,
 //  they cannot be complex EGCs, but only a single wchar_t, alas.
 int ncdirect_box(ncdirect* n, uint64_t ul, uint64_t ur,
                  uint64_t ll, uint64_t lr, const wchar_t* wchars,
-                 int ylen, int xlen, unsigned ctlword){
+                 unsigned ylen, unsigned xlen, unsigned ctlword){
   if(xlen < 2 || ylen < 2){
     return -1;
   }
@@ -1387,11 +1400,13 @@ int ncdirect_box(ncdirect* n, uint64_t ul, uint64_t ur,
   char vl[MB_LEN_MAX + 1];
   unsigned edges;
   edges = !(ctlword & NCBOXMASK_TOP) + !(ctlword & NCBOXMASK_LEFT);
+  // FIXME rewrite all fprintfs as ncdirect_putstr()!
   if(edges >= box_corner_needs(ctlword)){
     if(activate_channels(n, ul)){
       return -1;
     }
     if(fprintf(n->ttyfp, "%lc", wchars[0]) < 0){
+      logerror("error emitting %lc\n", wchars[0]);
       return -1;
     }
   }else{
@@ -1400,11 +1415,13 @@ int ncdirect_box(ncdirect* n, uint64_t ul, uint64_t ur,
   mbstate_t ps = {};
   size_t bytes;
   if((bytes = wcrtomb(hl, wchars[4], &ps)) == (size_t)-1){
+    logerror("error converting %lc\n", wchars[4]);
     return -1;
   }
   hl[bytes] = '\0';
   memset(&ps, 0, sizeof(ps));
   if((bytes = wcrtomb(vl, wchars[5], &ps)) == (size_t)-1){
+    logerror("error converting %lc\n", wchars[5]);
     return -1;
   }
   vl[bytes] = '\0';
@@ -1449,8 +1466,8 @@ int ncdirect_box(ncdirect* n, uint64_t ul, uint64_t ur,
     }else{
       ncdirect_cursor_left(n, xlen - 1);
     }
+    ncdirect_cursor_down(n, 1);
   }
-  ncdirect_cursor_down(n, 1);
   // bottom line
   edges = !(ctlword & NCBOXMASK_BOTTOM) + !(ctlword & NCBOXMASK_LEFT);
   if(edges >= box_corner_needs(ctlword)){
@@ -1486,13 +1503,13 @@ int ncdirect_box(ncdirect* n, uint64_t ul, uint64_t ur,
 
 int ncdirect_rounded_box(ncdirect* n, uint64_t ul, uint64_t ur,
                          uint64_t ll, uint64_t lr,
-                         int ylen, int xlen, unsigned ctlword){
+                         unsigned ylen, unsigned xlen, unsigned ctlword){
   return ncdirect_box(n, ul, ur, ll, lr, NCBOXROUNDW, ylen, xlen, ctlword);
 }
 
 int ncdirect_double_box(ncdirect* n, uint64_t ul, uint64_t ur,
-                         uint64_t ll, uint64_t lr,
-                         int ylen, int xlen, unsigned ctlword){
+                        uint64_t ll, uint64_t lr,
+                        unsigned ylen, unsigned xlen, unsigned ctlword){
   return ncdirect_box(n, ul, ur, ll, lr, NCBOXDOUBLEW, ylen, xlen, ctlword);
 }
 
diff --git a/src/lib/egcpool.h b/src/lib/egcpool.h
index 2f8808a5e3..5c896fc5b8 100644
--- a/src/lib/egcpool.h
+++ b/src/lib/egcpool.h
@@ -131,7 +131,9 @@ utf8_egc_len(const char* gcluster, int* colcount){
         }
       }
     }
-    *colcount += cols;
+    if(*colcount == 0){
+      *colcount += cols;
+    }
     ret += r;
     gcluster += r;
     prevw = wc;
diff --git a/src/lib/internal.h b/src/lib/internal.h
index c0c377e2b9..7bae31c4da 100644
--- a/src/lib/internal.h
+++ b/src/lib/internal.h
@@ -865,8 +865,9 @@ cell_duplicate_far(egcpool* tpool, nccell* targ, const ncplane* splane, const nc
 }
 
 int ncplane_resize_internal(ncplane* n, int keepy, int keepx,
-                            int keepleny, int keeplenx, int yoff, int xoff,
-                            int ylen, int xlen);
+                            unsigned keepleny, unsigned keeplenx,
+                            int yoff, int xoff,
+                            unsigned ylen, unsigned xlen);
 
 int update_term_dimensions(int* rows, int* cols, tinfo* tcache, int margin_b);
 
diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c
index 675dc9a14d..348081fdfd 100644
--- a/src/lib/notcurses.c
+++ b/src/lib/notcurses.c
@@ -692,12 +692,10 @@ int resize_callbacks_children(ncplane* n){
 }
 
 // can be used on stdplane, unlike ncplane_resize() which prohibits it.
-int ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
-                            int keeplenx, int yoff, int xoff, int ylen, int xlen){
-  if(keepleny < 0 || keeplenx < 0){ // can't retain negative size
-    logerror("Can't retain negative size %dx%d\n", keepleny, keeplenx);
-    return -1;
-  }
+int ncplane_resize_internal(ncplane* n, int keepy, int keepx,
+                            unsigned keepleny, unsigned keeplenx,
+                            int yoff, int xoff,
+                            unsigned ylen, unsigned xlen){
   if(keepy < 0 || keepx < 0){ // can't start at negative origin
     logerror("Can't retain negative offset %dx%d\n", keepy, keepx);
     return -1;
@@ -708,30 +706,30 @@ int ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
   }
   // can't be smaller than keep length
   if(ylen < keepleny){
-    logerror("Can't map in y dimension: %d < %d\n", ylen, keepleny);
+    logerror("Can't map in y dimension: %u < %d\n", ylen, keepleny);
     return -1;
   }
   if(xlen < keeplenx){
-    logerror("Can't map in x dimension: %d < %d\n", xlen, keeplenx);
+    logerror("Can't map in x dimension: %u < %d\n", xlen, keeplenx);
     return -1;
   }
   if(ylen <= 0 || xlen <= 0){ // can't resize to trivial or negative size
-    logerror("Can't achieve meaningless size %dx%d\n", ylen, xlen);
+    logerror("Can't achieve meaningless size %ux%u\n", ylen, xlen);
     return -1;
   }
   int rows, cols;
   ncplane_dim_yx(n, &rows, &cols);
-  if(keepleny + keepy > rows){
+  if(keepleny + keepy > (unsigned)rows){
     logerror("Can't keep %d@%d rows from %d\n", keepleny, keepy, rows);
     return -1;
   }
-  if(keeplenx + keepx > cols){
+  if(keeplenx + keepx > (unsigned)cols){
     logerror("Can't keep %d@%d cols from %d\n", keeplenx, keepx, cols);
     return -1;
   }
-  loginfo("%dx%d @ %d/%d → %d/%d @ %d/%d (want %dx%d@%d/%d)\n", rows, cols, n->absy, n->absx, ylen, xlen, n->absy + keepy + yoff, n->absx + keepx + xoff, keepleny, keeplenx, keepy, keepx);
+  loginfo("%dx%d @ %d/%d → %u/%u @ %d/%d (want %ux%u@%d/%d)\n", rows, cols, n->absy, n->absx, ylen, xlen, n->absy + keepy + yoff, n->absx + keepx + xoff, keepleny, keeplenx, keepy, keepx);
   if(n->absy == n->absy + keepy && n->absx == n->absx + keepx &&
-      rows == ylen && cols == xlen){
+      (unsigned)rows == ylen && (unsigned)cols == xlen){
     return 0;
   }
   notcurses* nc = ncplane_notcurses(n);
@@ -765,10 +763,10 @@ int ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
     }
   }
   // update the cursor, if it would otherwise be off-plane
-  if(n->y >= ylen){
+  if((unsigned)n->y >= ylen){
     n->y = ylen - 1;
   }
-  if(n->x >= xlen){
+  if((unsigned)n->x >= xlen){
     n->x = xlen - 1;
   }
   nccell* preserved = n->fb;
@@ -798,18 +796,18 @@ int ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
   // keepy..keepy + keepleny - 1 and columns keepx..keepx + keeplenx - 1.
   // anything else is zerod out. itery is the row we're writing *to*, and we
   // must write to each (and every cell in each).
-  for(int itery = 0 ; itery < ylen ; ++itery){
+  for(unsigned itery = 0 ; itery < ylen ; ++itery){
     int truey = itery + n->absy;
     int sourceoffy = truey - oldabsy;
 //fprintf(stderr, "sourceoffy: %d keepy: %d ylen: %d\n", sourceoffy, keepy, ylen);
     // if we have nothing copied to this line, zero it out in one go
-    if(sourceoffy < keepy || sourceoffy >= keepy + keepleny){
+    if(sourceoffy < keepy || sourceoffy >= keepy + (int)keepleny){
 //fprintf(stderr, "writing 0s to line %d of %d\n", itery, ylen);
       memset(fb + (itery * xlen), 0, sizeof(*fb) * xlen);
     }else{
       int copyoff = itery * xlen; // our target at any given time
       // we do have something to copy, and zero, one, or two regions to zero out
-      int copied = 0;
+      unsigned copied = 0;
       if(xoff < 0){
         memset(fb + copyoff, 0, sizeof(*fb) * -xoff);
         copyoff += -xoff;
@@ -831,8 +829,10 @@ int ncplane_resize_internal(ncplane* n, int keepy, int keepx, int keepleny,
   return resize_callbacks_children(n);
 }
 
-int ncplane_resize(ncplane* n, int keepy, int keepx, int keepleny,
-                   int keeplenx, int yoff, int xoff, int ylen, int xlen){
+int ncplane_resize(ncplane* n, int keepy, int keepx,
+                   unsigned keepleny, unsigned keeplenx,
+                   int yoff, int xoff,
+                   unsigned ylen, unsigned xlen){
   if(n == ncplane_notcurses(n)->stdplane){
 //fprintf(stderr, "Can't resize standard plane\n");
     return -1;
@@ -1877,8 +1877,12 @@ int ncplane_vprintf_stained(struct ncplane* n, const char* format, va_list ap){
   return ret;
 }
 
-int ncplane_hline_interp(ncplane* n, const nccell* c, int len,
+int ncplane_hline_interp(ncplane* n, const nccell* c, unsigned len,
                          uint64_t c1, uint64_t c2){
+  if(len <= 0){
+    logerror("passed invalid length %u\n", len);
+    return -1;
+  }
   unsigned ur, ug, ub;
   int r1, g1, b1, r2, g2, b2;
   int br1, bg1, bb1, br2, bg2, bb2;
@@ -1896,7 +1900,7 @@ int ncplane_hline_interp(ncplane* n, const nccell* c, int len,
   int deltbr = br2 - br1;
   int deltbg = bg2 - bg1;
   int deltbb = bb2 - bb1;
-  int ret;
+  unsigned ret;
   nccell dupc = CELL_TRIVIAL_INITIALIZER;
   if(nccell_duplicate(n, &dupc, c) < 0){
     return -1;
@@ -1909,12 +1913,12 @@ int ncplane_hline_interp(ncplane* n, const nccell* c, int len,
     bgdef = true;
   }
   for(ret = 0 ; ret < len ; ++ret){
-    int r = (deltr * ret) / len + r1;
-    int g = (deltg * ret) / len + g1;
-    int b = (deltb * ret) / len + b1;
-    int br = (deltbr * ret) / len + br1;
-    int bg = (deltbg * ret) / len + bg1;
-    int bb = (deltbb * ret) / len + bb1;
+    int r = (deltr * (int)ret) / (int)len + r1;
+    int g = (deltg * (int)ret) / (int)len + g1;
+    int b = (deltb * (int)ret) / (int)len + b1;
+    int br = (deltbr * (int)ret) / (int)len + br1;
+    int bg = (deltbg * (int)ret) / (int)len + bg1;
+    int bb = (deltbb * (int)ret) / (int)len + bb1;
     if(!fgdef){
       nccell_set_fg_rgb8(&dupc, r, g, b);
     }
@@ -1922,15 +1926,19 @@ int ncplane_hline_interp(ncplane* n, const nccell* c, int len,
       nccell_set_bg_rgb8(&dupc, br, bg, bb);
     }
     if(ncplane_putc(n, &dupc) <= 0){
-      break;
+      return -1;
     }
   }
   nccell_release(n, &dupc);
   return ret;
 }
 
-int ncplane_vline_interp(ncplane* n, const nccell* c, int len,
+int ncplane_vline_interp(ncplane* n, const nccell* c, unsigned len,
                          uint64_t c1, uint64_t c2){
+  if(len <= 0){
+    logerror("passed invalid length %u\n", len);
+    return -1;
+  }
   unsigned ur, ug, ub;
   int r1, g1, b1, r2, g2, b2;
   int br1, bg1, bb1, br2, bg2, bb2;
@@ -1942,13 +1950,14 @@ int ncplane_vline_interp(ncplane* n, const nccell* c, int len,
   br1 = ur; bg1 = ug; bb1 = ub;
   ncchannels_bg_rgb8(c2, &ur, &ug, &ub);
   br2 = ur; bg2 = ug; bb2 = ub;
-  int deltr = (r2 - r1) / (len + 1);
-  int deltg = (g2 - g1) / (len + 1);
-  int deltb = (b2 - b1) / (len + 1);
-  int deltbr = (br2 - br1) / (len + 1);
-  int deltbg = (bg2 - bg1) / (len + 1);
-  int deltbb = (bb2 - bb1) / (len + 1);
-  int ret, ypos, xpos;
+  int deltr = (r2 - r1) / ((int)len + 1);
+  int deltg = (g2 - g1) / ((int)len + 1);
+  int deltb = (b2 - b1) / ((int)len + 1);
+  int deltbr = (br2 - br1) / ((int)len + 1);
+  int deltbg = (bg2 - bg1) / ((int)len + 1);
+  int deltbb = (bb2 - bb1) / ((int)len + 1);
+  int ypos, xpos;
+  unsigned ret;
   ncplane_cursor_yx(n, &ypos, &xpos);
   nccell dupc = CELL_TRIVIAL_INITIALIZER;
   if(nccell_duplicate(n, &dupc, c) < 0){
@@ -1978,7 +1987,7 @@ int ncplane_vline_interp(ncplane* n, const nccell* c, int len,
       nccell_set_bg_rgb8(&dupc, br1, bg1, bb1);
     }
     if(ncplane_putc(n, &dupc) <= 0){
-      break;
+      return -1;
     }
   }
   nccell_release(n, &dupc);
@@ -1987,23 +1996,23 @@ int ncplane_vline_interp(ncplane* n, const nccell* c, int len,
 
 int ncplane_box(ncplane* n, const nccell* ul, const nccell* ur,
                 const nccell* ll, const nccell* lr, const nccell* hl,
-                const nccell* vl, int ystop, int xstop,
+                const nccell* vl, unsigned ystop, unsigned xstop,
                 unsigned ctlword){
   int yoff, xoff, ymax, xmax;
   ncplane_cursor_yx(n, &yoff, &xoff);
   // must be at least 2x2, with its upper-left corner at the current cursor
-  if(ystop < yoff + 1){
-    logerror("ystop (%d) insufficient for yoff (%d)\n", ystop, yoff);
+  if(ystop < (unsigned)yoff + 1){
+    logerror("ystop (%u) insufficient for yoff (%d)\n", ystop, yoff);
     return -1;
   }
-  if(xstop < xoff + 1){
-    logerror("xstop (%d) insufficient for xoff (%d)\n", xstop, xoff);
+  if(xstop < (unsigned)xoff + 1){
+    logerror("xstop (%u) insufficient for xoff (%d)\n", xstop, xoff);
     return -1;
   }
   ncplane_dim_yx(n, &ymax, &xmax);
   // must be within the ncplane
-  if(xstop >= xmax || ystop >= ymax){
-    logerror("Boundary (%dx%d) beyond plane (%dx%d)\n", ystop, xstop, ymax, xmax);
+  if(xstop >= (unsigned)xmax || ystop >= (unsigned)ymax){
+    logerror("Boundary (%ux%u) beyond plane (%dx%d)\n", ystop, xstop, ymax, xmax);
     return -1;
   }
   unsigned edges;
@@ -2040,7 +2049,7 @@ int ncplane_box(ncplane* n, const nccell* ul, const nccell* ur,
   }
   ++yoff;
   // middle rows (vertical lines)
-  if(yoff < ystop){
+  if((unsigned)yoff < ystop){
     if(!(ctlword & NCBOXMASK_LEFT)){
       if(ncplane_cursor_move_yx(n, yoff, xoff)){
         return -1;
@@ -2837,44 +2846,36 @@ is_bg_p(int idx, int py, int px, int width){
 
 static inline uint32_t*
 ncplane_as_rgba_internal(const ncplane* nc, ncblitter_e blit,
-                         int begy, int begx, int leny, int lenx,
-                         int* pxdimy, int* pxdimx){
+                         unsigned begy, unsigned begx, unsigned leny,
+                         unsigned lenx, unsigned* pxdimy, unsigned* pxdimx){
   const notcurses* ncur = ncplane_notcurses_const(nc);
-  if(begy < 0 || begx < 0){
-    logerror("Nil offset (%d,%d)\n", begy, begx);
-    return NULL;
-  }
-  if(begx >= nc->lenx || begy >= nc->leny){
-    logerror("Invalid offset (%d,%d)\n", begy, begx);
+  if(begx >= (unsigned)nc->lenx || begy >= (unsigned)nc->leny){
+    logerror("invalid origin (%u,%u)\n", begy, begx);
     return NULL;
   }
-  if(lenx == -1){ // -1 means "to the end"; use all space available
+  if(lenx == 0){ // -1 means "to the end"; use all space available
     lenx = nc->lenx - begx;
   }
-  if(leny == -1){
+  if(leny == 0){
     leny = nc->leny - begy;
   }
-  if(lenx <= 0 || leny <= 0){ // no need to draw zero-size object, exit
-    logerror("Nil geometry (%dx%d)\n", leny, lenx);
-    return NULL;
-  }
 //fprintf(stderr, "sum: %d/%d avail: %d/%d\n", begy + leny, begx + lenx, nc->leny, nc->lenx);
-  if(begx + lenx > nc->lenx || begy + leny > nc->leny){
-    logerror("Invalid specs %d + %d > %d or %d + %d > %d\n",
+  if(nc->lenx - begx < lenx || nc->leny - begy < leny){
+    logerror("invalid specs %u + %u > %d or %u + %u > %d\n",
              begx, lenx, nc->lenx, begy, leny, nc->leny);
     return NULL;
   }
   if(blit == NCBLIT_PIXEL){ // FIXME extend this to support sprixels
-    logerror("Pixel blitter %d not yet supported\n", blit);
+    logerror("pixel blitter %d not yet supported\n", blit);
     return NULL;
   }
   if(blit == NCBLIT_DEFAULT){
-    logerror("Must specify exact blitter, not NCBLIT_DEFAULT\n");
+    logerror("must specify exact blitter, not NCBLIT_DEFAULT\n");
     return NULL;
   }
   const struct blitset* bset = lookup_blitset(&ncur->tcache, blit, false);
   if(bset == NULL){
-    logerror("Blitter %d invalid in current environment\n", blit);
+    logerror("blitter %d invalid in current environment\n", blit);
     return NULL;
   }
 //fprintf(stderr, "ALLOCATING %u %d %d %p\n", 4u * lenx * leny * 2, leny, lenx, bset);
@@ -2887,8 +2888,8 @@ ncplane_as_rgba_internal(const ncplane* nc, ncblitter_e blit,
   uint32_t* ret = malloc(sizeof(*ret) * lenx * bset->width * leny * bset->height);
 //fprintf(stderr, "GEOM: %d/%d %d/%d ret: %p\n", bset->height, bset->width, *pxdimy, *pxdimx, ret);
   if(ret){
-    for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += bset->height){
-      for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, targx += bset->width){
+    for(unsigned y = begy, targy = 0 ; y < begy + leny ; ++y, targy += bset->height){
+      for(unsigned x = begx, targx = 0 ; x < begx + lenx ; ++x, targx += bset->width){
         uint16_t stylemask;
         uint64_t channels;
         char* c = ncplane_at_yx(nc, y, x, &stylemask, &channels);
@@ -2941,9 +2942,9 @@ ncplane_as_rgba_internal(const ncplane* nc, ncblitter_e blit,
 }
 
 uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
-                          int begy, int begx, int leny, int lenx,
-                          int* pxdimy, int* pxdimx){
-  int px, py;
+                          unsigned begy, unsigned begx, unsigned leny,
+                          unsigned lenx, unsigned* pxdimy, unsigned* pxdimx){
+  unsigned px, py;
   if(!pxdimy){
     pxdimy = &py;
   }
@@ -2954,36 +2955,28 @@ uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
 }
 
 // return a heap-allocated copy of the contents
-char* ncplane_contents(ncplane* nc, int begy, int begx, int leny, int lenx){
-  if(begy < 0 || begx < 0){
-    logerror("Beginning coordinates (%d/%d) below 0\n", begy, begx);
-    return NULL;
-  }
-  if(begx >= nc->lenx || begy >= nc->leny){
-    logerror("Beginning coordinates (%d/%d) exceeded lengths (%d/%d)\n",
+char* ncplane_contents(ncplane* nc, unsigned begy, unsigned begx, unsigned leny, unsigned lenx){
+  if(begx >= (unsigned)nc->lenx || begy >= (unsigned)nc->leny){
+    logerror("beginning coordinates (%u/%u) exceeded area (%d/%d)\n",
              begy, begx, nc->leny, nc->lenx);
     return NULL;
   }
-  if(lenx == -1){ // -1 means "to the end"; use all space available
+  if(lenx == 0){ // 0 means "to the end"; use all space available
     lenx = nc->lenx - begx;
   }
-  if(leny == -1){
+  if(leny == 0){
     leny = nc->leny - begy;
   }
-  if(lenx < 0 || leny < 0){ // no need to draw zero-size object, exit
-    logerror("Lengths (%d/%d) below 0\n", leny, lenx);
-    return NULL;
-  }
-  if(begx + lenx > nc->lenx || begy + leny > nc->leny){
-    logerror("Ending coordinates (%d/%d) exceeded lengths (%d/%d)\n",
+  if(nc->lenx - begx < lenx || nc->leny - begy < leny){
+    logerror("ending coordinates (%u/%u) exceeded lengths (%d/%d)\n",
              begy + leny, begx + lenx, nc->leny, nc->lenx);
     return NULL;
   }
   size_t retlen = 1;
   char* ret = malloc(retlen);
   if(ret){
-    for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += 2){
-      for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, ++targx){
+    for(unsigned y = begy, targy = 0 ; y < begy + leny ; ++y, targy += 2){
+      for(unsigned x = begx, targx = 0 ; x < begx + lenx ; ++x, ++targx){
         nccell ncl = CELL_TRIVIAL_INITIALIZER;
         // we need ncplane_at_yx_cell() here instead of ncplane_at_yx(),
         // because we should only have one copy of each wide EGC.
diff --git a/src/lib/reader.c b/src/lib/reader.c
index d92d00a95b..cda56647f7 100644
--- a/src/lib/reader.c
+++ b/src/lib/reader.c
@@ -381,7 +381,7 @@ bool ncreader_offer_input(ncreader* n, const ncinput* ni){
 }
 
 char* ncreader_contents(const ncreader* n){
-  return ncplane_contents(n->ncp, 0, 0, -1, -1);
+  return ncplane_contents(n->ncp, 0, 0, 0, 0);
 }
 
 void ncreader_destroy(ncreader* n, char** contents){
diff --git a/src/lib/render.c b/src/lib/render.c
index 479b20eb1a..1dfaab8993 100644
--- a/src/lib/render.c
+++ b/src/lib/render.c
@@ -526,31 +526,44 @@ postpaint(const tinfo* ti, nccell* lastframe, int dimy, int dimx,
 // merging one plane down onto another is basically just performing a render
 // using only these two planes, with the result written to the lower plane.
 int ncplane_mergedown(ncplane* restrict src, ncplane* restrict dst,
-                      int begsrcy, int begsrcx, int leny, int lenx,
-                      int dsty, int dstx){
+                      unsigned begsrcy, unsigned begsrcx,
+                      unsigned leny, unsigned lenx,
+                      unsigned dsty, unsigned dstx){
 //fprintf(stderr, "Merging down %d/%d @ %d/%d to %d/%d\n", leny, lenx, begsrcy, begsrcx, dsty, dstx);
-  if(dsty >= dst->leny || dstx >= dst->lenx){
-    logerror("Dest origin %d/%d ≥ dest dimensions %d/%d\n",
+  if(dsty >= (unsigned)dst->leny || dstx >= (unsigned)dst->lenx){
+    logerror("dest origin %u/%u ≥ dest dimensions %d/%d\n",
              dsty, dstx, dst->leny, dst->lenx);
     return -1;
   }
   if(dst->leny - leny < dsty || dst->lenx - lenx < dstx){
-    logerror("Dest len %d/%d ≥ dest dimensions %d/%d\n",
+    logerror("dest len %u/%u ≥ dest dimensions %d/%d\n",
              leny, lenx, dst->leny, dst->lenx);
     return -1;
   }
-  if(begsrcy >= src->leny || begsrcx >= src->lenx){
-    logerror("Source origin %d/%d ≥ source dimensions %d/%d\n",
+  if(begsrcy >= (unsigned)src->leny || begsrcx >= (unsigned)src->lenx){
+    logerror("source origin %u/%u ≥ source dimensions %d/%d\n",
              begsrcy, begsrcx, src->leny, src->lenx);
     return -1;
   }
+  if(leny == 0){
+    if((leny = src->leny - begsrcy) == 0){
+      logerror("source area was zero height\n");
+      return -1;
+    }
+  }
+  if(lenx == 0){
+    if((lenx = src->lenx - begsrcx) == 0){
+      logerror("source area was zero width\n");
+      return -1;
+    }
+  }
   if(src->leny - leny < begsrcy || src->lenx - lenx < begsrcx){
-    logerror("Source len %d/%d ≥ source dimensions %d/%d\n",
+    logerror("source len %u/%u ≥ source dimensions %d/%d\n",
              leny, lenx, src->leny, src->lenx);
     return -1;
   }
   if(src->sprite || dst->sprite){
-    logerror("Can't merge sprixel planes\n");
+    logerror("can't merge sprixel planes\n");
     return -1;
   }
   const int totalcells = dst->leny * dst->lenx;
@@ -558,7 +571,7 @@ int ncplane_mergedown(ncplane* restrict src, ncplane* restrict dst,
   const size_t crenderlen = sizeof(struct crender) * totalcells;
   struct crender* rvec = malloc(crenderlen);
   if(!rendfb || !rvec){
-    logerror("Error allocating render state for %dx%d\n", leny, lenx);
+    logerror("error allocating render state for %ux%u\n", leny, lenx);
     free(rendfb);
     free(rvec);
     return -1;
@@ -580,14 +593,7 @@ int ncplane_mergedown(ncplane* restrict src, ncplane* restrict dst,
 }
 
 int ncplane_mergedown_simple(ncplane* restrict src, ncplane* restrict dst){
-  // have to check dst, since we used to accept a NULL dst to mean the
-  // standard plane (this was unsafe, since src might be in another pile).
-  if(dst == NULL){
-    return -1;
-  }
-  int dimy, dimx;
-  ncplane_dim_yx(dst, &dimy, &dimx);
-  return ncplane_mergedown(src, dst, 0, 0, ncplane_dim_y(src), ncplane_dim_x(src), 0, 0);
+  return ncplane_mergedown(src, dst, 0, 0, 0, 0, 0, 0);
 }
 
 // write the nccell's UTF-8 extended grapheme cluster to the provided FILE*.
diff --git a/src/lib/visual.c b/src/lib/visual.c
index 190b7e86cd..1bfaffe6ce 100644
--- a/src/lib/visual.c
+++ b/src/lib/visual.c
@@ -1190,9 +1190,10 @@ ncplane* ncvisual_render(notcurses* nc, ncvisual* ncv, const struct ncvisual_opt
   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;
+ncvisual* ncvisual_from_plane(const ncplane* n, ncblitter_e blit,
+                              unsigned begy, unsigned begx,
+                              unsigned leny, unsigned lenx){
+  unsigned py, px;
   uint32_t* rgba = ncplane_as_rgba(n, blit, begy, begx, leny, lenx, &py, &px);
 //fprintf(stderr, "snarg: %d/%d @ %d/%d (%p)\n", leny, lenx, begy, begx, rgba);
   if(rgba == NULL){
diff --git a/src/tetris/background.h b/src/tetris/background.h
index ec85179bf7..650fd2bbd2 100644
--- a/src/tetris/background.h
+++ b/src/tetris/background.h
@@ -18,7 +18,7 @@ void DrawBoard() { // draw all fixed components of the game
   stdplane_->get_dim(&y, &x);
   board_top_y_ = y - (BOARD_HEIGHT + 2);
   board_ = std::make_unique<ncpp::Plane>(BOARD_HEIGHT, BOARD_WIDTH * 2,
-                                          board_top_y_, x / 2 - (BOARD_WIDTH + 1));
+                                         board_top_y_, x / 2 - (BOARD_WIDTH + 1));
   uint64_t channels = 0;
   ncchannels_set_fg_rgb(&channels, 0x00b040);
   ncchannels_set_bg_alpha(&channels, NCALPHA_TRANSPARENT);


Test patch:
diff --git a/src/tests/Ncpp.cpp b/src/tests/Ncpp.cpp
index 4b1f6d0001..1f39355961 100644
--- a/src/tests/Ncpp.cpp
+++ b/src/tests/Ncpp.cpp
@@ -94,7 +94,7 @@ TEST_CASE("Ncpp"
       REQUIRE(n);
       // FIXME load it into visual, erase plane, render visual, check for equivalence...
       {
-        Visual v = Visual(*n, NCBLIT_1x1, 0, 0, -1, -1);
+        Visual v = Visual(*n, NCBLIT_1x1, 0, 0, 0, 0);
       }
     }
     CHECK(nc.stop());
diff --git a/src/tests/blit.cpp b/src/tests/blit.cpp
index 4bf6124dc1..727ebf741e 100644
--- a/src/tests/blit.cpp
+++ b/src/tests/blit.cpp
@@ -166,8 +166,8 @@ TEST_CASE("Blit") {
     CHECK(0 == notcurses_render(nc_));
     CHECK(1 == ncplane_dim_y(p));
     CHECK(2 == ncplane_dim_x(p));
-    int pxdimy, pxdimx;
-    auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, -1, -1, &pxdimy, &pxdimx);
+    unsigned pxdimy, pxdimx;
+    auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, 0, 0, &pxdimy, &pxdimx);
     REQUIRE(nullptr != edata);
     CHECK(0 == memcmp(data, edata, sizeof(data)));
     free(edata);
@@ -193,8 +193,8 @@ TEST_CASE("Blit") {
       REQUIRE(nullptr != p);
       CHECK(1 == ncplane_dim_y(p));
       CHECK(4 == ncplane_dim_x(p));
-      int pxdimy, pxdimx;
-      auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, -1, -1, &pxdimy, &pxdimx);
+      unsigned pxdimy, pxdimx;
+      auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, 0, 0, &pxdimy, &pxdimx);
       REQUIRE(nullptr != edata);
       for(size_t i = 0 ; i < sizeof(data) / sizeof(*data) ; ++i){
         CHECK(edata[i] == data[i]);
@@ -238,8 +238,8 @@ TEST_CASE("Blit") {
       REQUIRE(nullptr != p);
       CHECK(1 == ncplane_dim_y(p));
       CHECK(16 == ncplane_dim_x(p));
-      int pxdimy, pxdimx;
-      auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, -1, -1, &pxdimy, &pxdimx);
+      unsigned pxdimy, pxdimx;
+      auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, 0, 0, &pxdimy, &pxdimx);
       REQUIRE(nullptr != edata);
       for(size_t i = 0 ; i < sizeof(data) / sizeof(*data) ; ++i){
         CHECK(edata[i] == data[i]);
diff --git a/src/tests/cell.cpp b/src/tests/cell.cpp
index a4693f73e8..7c1ee906d5 100644
--- a/src/tests/cell.cpp
+++ b/src/tests/cell.cpp
@@ -22,18 +22,10 @@ TEST_CASE("Cell") {
     CHECK(1 == nccell_cols(&c));
     CHECK(4 == nccell_load(n_, &c, " ி"));
     cols = nccell_cols(&c);
-#if defined(__linux__) || defined(__APPLE__)
-    CHECK(2 == cols);
-#else
     CHECK(1 == cols);
-#endif
     CHECK(4 == nccell_load(n_, &c, " ि"));
     cols = nccell_cols(&c);
-#if defined(__linux__) || defined(__APPLE__)
-    CHECK(2 == cols);
-#else
     CHECK(1 == cols);
-#endif
     // musl+s390x (alpine) is reporting these EGCs to be 0 columns wide (they
     // ought be 1). not sure whether i've got a bug (s390x is big-endian), or
     // whether it does. just relaxed the tests for now FIXME.
diff --git a/src/tests/direct.cpp b/src/tests/direct.cpp
index 2c6cf15faf..e611037f66 100644
--- a/src/tests/direct.cpp
+++ b/src/tests/direct.cpp
@@ -68,7 +68,10 @@ TEST_CASE("Direct") {
     ncchannels_set_bg_default(&chans);
     ncdirect_set_bg_rgb8(nc_, 0x88, 0x88, 0x88);
     printf("test background\n");
-    CHECK(0 == ncdirect_ascii_box(nc_, chans, chans, chans, chans, 8, 8, 0));
+    for(unsigned r = 2 ; r < 8 ; ++r){
+      CHECK(0 == ncdirect_ascii_box(nc_, chans, chans, chans, chans, r, r, 0));
+      printf("\n");
+    }
   }
 
 #ifndef NOTCURSES_USE_MULTIMEDIA
diff --git a/src/tests/palette.cpp b/src/tests/palette.cpp
index 9b673f0665..9e8970fd8c 100644
--- a/src/tests/palette.cpp
+++ b/src/tests/palette.cpp
@@ -91,7 +91,9 @@ TEST_CASE("Palette256") {
     nccell_release(n_, &c);
     CHECK(0 == notcurses_render(nc_));
     nccell r = CELL_TRIVIAL_INITIALIZER;
-    CHECK(nullptr != notcurses_at_yx(nc_, 0, 0, &r.stylemask, &r.channels));
+    auto egc = notcurses_at_yx(nc_, 0, 0, &r.stylemask, &r.channels);
+    CHECK(nullptr != egc);
+    free(egc);
     CHECK(nccell_fg_palindex_p(&r));
     CHECK(nccell_bg_palindex_p(&r));
     CHECK(NCALPHA_OPAQUE == nccell_fg_alpha(&r));
diff --git a/src/tests/rotate.cpp b/src/tests/rotate.cpp
index 258b25003a..0d7abc11cd 100644
--- a/src/tests/rotate.cpp
+++ b/src/tests/rotate.cpp
@@ -175,9 +175,9 @@ TEST_CASE("Rotate") {
     opts.flags = NCVISUAL_OPTION_CHILDPLANE;
     auto rendered = ncvisual_blit(nc_, ncv, &opts);
     REQUIRE(rendered);
-    int pxdimy, pxdimx;
+    unsigned pxdimy, pxdimx;
     uint32_t* rgbaret = ncplane_as_rgba(rendered, NCBLIT_2x1,
-                                        0, 0, -1, -1, &pxdimy, &pxdimx);
+                                        0, 0, 0, 0, &pxdimy, &pxdimx);
     REQUIRE(rgbaret);
     if(height % 2){
       ++height;
@@ -237,9 +237,9 @@ TEST_CASE("Rotate") {
     opts.flags = NCVISUAL_OPTION_CHILDPLANE;
     auto rendered = ncvisual_blit(nc_, ncv, &opts);
     REQUIRE(rendered);
-    int pxdimy, pxdimx;
+    unsigned pxdimy, pxdimx;
     uint32_t* rgbaret = ncplane_as_rgba(rendered, NCBLIT_2x1,
-                                        0, 0, -1, -1, &pxdimy, &pxdimx);
+                                        0, 0, 0, 0, &pxdimy, &pxdimx);
     REQUIRE(rgbaret);
     if(height % 2){
       ++height;
diff --git a/src/tests/selector.cpp b/src/tests/selector.cpp
index 02cc4c285e..03448ff43d 100644
--- a/src/tests/selector.cpp
+++ b/src/tests/selector.cpp
@@ -37,7 +37,8 @@ TEST_CASE("Selectors") {
 
   SUBCASE("TitledSelector") {
     struct ncselector_options opts{};
-    opts.title = strdup("hey hey whaddya say");
+    auto title = strdup("hey hey whaddya say");
+    opts.title = title;
     struct ncplane_options nopts = {
       .y = 0,
       .x = 0,
@@ -57,11 +58,13 @@ TEST_CASE("Selectors") {
     CHECK(6 == dimy);
     CHECK(strlen(opts.title) + 4 == dimx);
     ncselector_destroy(ncs, nullptr);
+    free(title);
   }
 
   SUBCASE("SecondarySelector") {
     struct ncselector_options opts{};
-    opts.secondary = strdup("this is not a title, but it's not *not* a title");
+    auto secondary = strdup("this is not a title, but it's not *not* a title");
+    opts.secondary = secondary;
     struct ncplane_options nopts = {
       .y = 0,
       .x = 0,
@@ -81,11 +84,13 @@ TEST_CASE("Selectors") {
     CHECK(4 == dimy);
     CHECK(strlen(opts.secondary) + 2 == dimx);
     ncselector_destroy(ncs, nullptr);
+    free(secondary);
   }
 
   SUBCASE("FooterSelector") {
     struct ncselector_options opts{};
-    opts.footer = strdup("i am a lone footer, little old footer");
+    auto foot = strdup("i am a lone footer, little old footer");
+    opts.footer = foot;
     struct ncplane_options nopts = {
       .y = 0,
       .x = 0,
@@ -105,6 +110,7 @@ TEST_CASE("Selectors") {
     CHECK(4 == dimy);
     CHECK(strlen(opts.footer) + 2 == dimx);
     ncselector_destroy(ncs, nullptr);
+    free(foot);
   }
 
   SUBCASE("PopulatedSelector") {
diff --git a/src/tests/textlayout.cpp b/src/tests/textlayout.cpp
index 24a43df78c..269f9297b0 100644
--- a/src/tests/textlayout.cpp
+++ b/src/tests/textlayout.cpp
@@ -105,7 +105,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "my nuclear arms"));
     free(line);
@@ -132,7 +132,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "my grasping arms"));
     free(line);
@@ -159,7 +159,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "abcde")); // FIXME should have newlines
     free(line);
@@ -186,7 +186,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "abcdefgh"));
     free(line);
@@ -214,7 +214,7 @@ TEST_CASE("TextLayout") {
       CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
       CHECK(0 == notcurses_render(nc_));
       CHECK(bytes == strlen(boundstr));
-      char* line = ncplane_contents(sp, 0, 0, -1, -1);
+      char* line = ncplane_contents(sp, 0, 0, 0, 0);
       REQUIRE(line);
       CHECK(0 == strcmp(line, boundstr));
       free(line);
@@ -243,7 +243,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "my thermonuclear arms"));
     free(line);
@@ -272,7 +272,7 @@ TEST_CASE("TextLayout") {
       CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
       CHECK(0 == notcurses_render(nc_));
       CHECK(bytes == strlen(boundstr));
-      char* line = ncplane_contents(sp, 0, 0, -1, -1);
+      char* line = ncplane_contents(sp, 0, 0, 0, 0);
       REQUIRE(line);
       CHECK(0 == strcmp(line, "1 我能吞下玻璃"));
       free(line);
@@ -299,7 +299,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "    my thermonuclear arms"));
     free(line);
@@ -326,7 +326,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "quarkgluonfart "));
     free(line);
@@ -353,7 +353,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "quark gluon fart "));
     free(line);
@@ -380,7 +380,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "quantum balls"));
     free(line);
@@ -407,7 +407,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "quantum balls scratchy no?! "));
     free(line);
@@ -434,7 +434,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "0123456789AB"));
     free(line);
@@ -462,7 +462,7 @@ TEST_CASE("TextLayout") {
       CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
       CHECK(0 == notcurses_render(nc_));
       CHECK(bytes == strlen(boundstr));
-      char* line = ncplane_contents(sp, 0, 0, -1, -1);
+      char* line = ncplane_contents(sp, 0, 0, 0, 0);
       REQUIRE(line);
       CHECK(0 == strcmp(line, "我能吞 下玻璃 "));
       free(line);
@@ -492,7 +492,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 > res);
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes < strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "quantum balls scratchy no?! "));
     free(line);
@@ -519,7 +519,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
     CHECK(0 == notcurses_render(nc_));
     CHECK(bytes == strlen(boundstr));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "scratchy?! true! arrrrp"));
     free(line);
@@ -553,7 +553,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, text, &bytes));
     CHECK(bytes == strlen(text));
     CHECK(0 == notcurses_render(nc_));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornare neque ac ipsum viverra, vestibulum hendrerit leo consequat. Integer velit, pharetra sed nisl quis, porttitor ornare purus. Cras ac sollicitudin dolor, eget elementum dolor. Quisque lobortis sagittis."));
     free(line);
@@ -589,7 +589,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, text, &bytes));
     CHECK(bytes == strlen(text));
     CHECK(0 == notcurses_render(nc_));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "to be selected from a list of n items. NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output. A variety of plots are supported, and menus can be placed along the top and/or bottom of any plane.Widgets can be controlled with the keyboard and/or mouse. They are implemented atop ncplanes, and these planes can be manipulated like all others."));
     free(line);
@@ -626,7 +626,7 @@ TEST_CASE("TextLayout") {
     CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, text, &bytes));
     CHECK(bytes == strlen(text));
     CHECK(0 == notcurses_render(nc_));
-    char* line = ncplane_contents(sp, 0, 0, -1, -1);
+    char* line = ncplane_contents(sp, 0, 0, 0, 0);
     REQUIRE(line);
     CHECK(0 == strcmp(line, "Notcurses provides several widgets to quickly build vivid TUIs.This NCReader widget facilitates free-form text entry complete with readline-style bindings. NCSelector allows a single option to be selected from a list. NCMultiselector allows 0..n options to be selected from a list of n items. NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output. A variety of plots are supported, and menus can be placed along the top and/or bottom of any plane.Widgets can be controlled with the keyboard and/or mouse. They are implemented atop ncplanes, and these planes can be manipulated like all others."));
     free(line);