ID:2093199
 
Not a bug
BYOND Version:510
Operating System:Windows 7 Ultimate 64-bit
Web Browser:Chrome 50.0.2661.102
Applies to:Dream Daemon
Status: Not a bug

This is not a bug. It may be an incorrect use of syntax or a limitation in the software. For further discussion on the matter, please consult the BYOND forums.
Descriptive Problem Summary:
So, since you said that one thing you said about how you technically aren't suppose to make childs of atom/movable, i fully understand if you don't fix this, but, it's still technically unexpected behavior. =P

/atom/movable/light
mob
verb/testAM()
var/atom/movable/light/thing = new()
world << isobj(thing)
world << istype(thing, /obj)

(you get 1 and 0)

workaround:
#define isobj(thing) istype(thing, /obj)


Edit, I'm almost 100% sure this can't be fixed since atom/movable and obj are the same typeid, so isobj() would be slower if it was made to not use that
Yeah, I'm gonna mark this down as a non-bug; this is the kind of oddity that can crop up if you define things under /atom/movable instead of explicitly under /obj or /mob, and the results you're seeing are actually correct.

Since it doesn't inherit from the /obj type, but from /atom/movable instead, the istype() check is supposed to return false just like it does. But because internally it actually is an obj--it absolutely has to be either an obj or a mob if it descends from /atom/movable--isobj() returns true just like it does.

Basically the obj internal structure and the /obj type are separate entities, but they're meant to stay together. By inheriting directly from /atom/movable you create an object that has the structure without the type. isobj() simply checks whether the value is of internal type 2, an obj, and doesn't concern itself with type inheritance (which also means it's way faster).
Lummox JR resolved issue (Not a bug)
I'm guessing istype() actually checks the type and path related things, while isobj() directly checks _dm_interface, or something silly like that?
To make things faster, all types are assigned a typeid, the built-in types have specific typeids, the isobj(), ismob() and the sort do a direct typeid check. istype() needs to be a lot less generalized so I imagine a typeid check wouldn't be feasible for it.
To be more specific, every value in BYOND is a Value struct that contains a 4-byte identifier or number, and a 1-byte type. The 1-byte type for an obj is 2, mobs are 3, and so on; it has no direct relationship to the type path. isobj() is basically just a quick check that the value is an OBJ_REF (2) type.

The type path is a prototype ID, and it's stored with the obj's structure. istype() looks up the prototype and goes up the prototype tree as far as it needs to until it either finds the target type ID, or reaches the root without a match.
In response to Lummox JR
Lummox JR wrote:
By inheriting directly from /atom/movable you create an object that has the structure without the type.

To be honest that is the intention of inherenting directly from movable. Something movable that can be placed on the world but isn't an obj and inherits none of the obj procs or vars and fails a obj is type. The only hiccup was isobj. My workaround will do us nicely, we assumed is* were shortcuts of is types and now that we know this isn't the case we will just override them with macros that map to is type.

https://github.com/tgstation/tgstation/pull/18161/ files?diff=unified
Only where required though, MSO.
since /atom types become turfs, and /atom/movables become /objs there's no need to override say, isarea() or ismob(), those can't be separated from their paths (as far as I'm aware), so those retaining their ID checks instead of path ones is a nice boost to keep around.
In response to MrStonedOne
MrStonedOne wrote:
inherenting

there's no need to override say, isarea() or ismob(),

For now, waiting for when atom/movable starts matching true for ismob() =P
Having come upon this oddity recently I just want to say that this is so completely unintuitive as far as oddities of DM go.

Not saying it doesn't make sense for a low-level standpoint. But isobj as a name doesn't seem to fit what's actually happening in an isobj check and that that's silly.
In response to Pseudonym
it does, it's checking if it fits the internal definition of an "obj", your definition (and previously pretty much everyone's) of obj was simply anything derived from /obj, and /obj itself, this turned out to be wrong.