We are on the cusp of a pretty important release for BYOND, in which for the first time icons bigger than one tile can actually be treated as bigger than one tile without requiring extra libraries.
To facilitate this, two new vars have been added to the language, and these exist for movable atoms. The vars are bounds and locs.
The locs var is a list of all turfs covered by the atom. By default, this is just a list with one item: the turf the atom is on. (If the atom is in another movable, then the list just contains their current loc, or it can be empty if the atom has no loc at all.)
The way to make an atom bigger than one tile is to use the bounds var. This is null by default, for reasons explained below, but basically its purpose is to tell the server that the obj or mob has a non-default physical size. In an ordinary topdown map with a 32×32 icon size, "32,32" would be a simple one-tile object. But if you were to change it to "64,64", the atom would be 64 pixels wide and 64 high, giving it a 2×2 tile span. Then, as long as the atom is at a valid place on the map it should have four turfs in the locs list. The width and height you need will be based on your chosen world.icon_size and world.map_format.
Big atoms do a little bit more work in Move(). Instead of checking just one turf at a time in Enter() and Exit() , they have to check multiple turfs. Exited() and Entered() get called for multiple turfs as well. Right now this also means that multiple turfs can be bumped, so Bump() will be called once for each turf (or whatever obstacle is on it) that blocks movement, instead of just calling Bump() for one.
You might ask at this point how the contents list is impacted by this change. After all, a big atom is actually covering multiple turfs, isn't it? Yup, it certainly is. If you use bounds to define a big atom, it will appear in the contents list of any turfs that it "overhangs". (Every atom still has just one true loc, which is still at the southwest corner where it has always been, but if the atom is bigger than one tile, it will overhang other turfs.) If you deliberately add a movable atom to a turf's contents, it will still move to that turf as its new loc, just as it always has.
One of our goals in adding the bounds var is that eventually, we'd like to have support for native pixel movement. In such a system, you could take a simple 16x16 smiley icon and give its obj bounds of "9,9 to 24,24" that tell the server it has a width of 16 pixels and that the leftover 8 pixels around it in every direction aren't considered part of the obj. (You could of course just use a 16x16 icon and set the bounds to "16,16".) For the moment though, bounds is strictly width and height only. Right now because pixel offsets aren't taken into account yet, there's no such thing as a partially covered tile; a width of 33 still covers two tiles so it's equivalent to a width of 64.
This is why bounds is null by default. In the future when pixel movement is supported natively, we'll want pixel offsets to be more than just visual. By giving an obj or mob explicit bounds, that tells the server that its pixel offsets (x and y, not z) matter. So you could have a one-tile obj with bounds of "32x32" and a pixel_x value of 16, and it would straddle two turfs. But leaving bounds null means the pixel offsets work just like they do now, as strictly a visual concern. This way, older games can maintain their existing functionality without having to make any changes.
Again, pixel offsets are planned to become more than just visual in a future release, though only for atoms that use the bounds var. For right now, the value of locs is unaffected by those offsets. Also for the time being, "set src in usr.loc" still only applies to the actual direct value of usr.loc, not to all locs, but this is something we may also change in the future. Verbs currently respect the expanded boundaries provided by bounds, so that if a big obj peeks into range of your verb it can be used, and view() and range() also take big atoms into account.
Using bounds will work in projects using ISOMETRIC_MAP and SIDE_MAP as well, although visually there are layering issues that crop up when mixing isometric mode and big atoms. In these modes the "footprint" of every tile is square, so the tile height used for bounds is based on the width of your icons, not the height. For instance in a SIDE_MAP project with 64×48 tiles, a one-tile bounds would be "64,64" because that's how the tile would look from a bird's-eye view.
Expect these changes in version 490, to be released later this week. As noted, we plan to leverage these features to provide native pixel movement in a future update. In the meantime, take advantage of the numerous methods to do this already within DM code.