"There's nothing there" is pretty close. I see OOP as fundamentally one key concept: that there are functions which "belong to" specific types, and all other operations on those types go through the functions that "belong to" them.
You can do OOP in a language like C by writing your code as structs and accompanying functions that operate on them. You might expose the structs to the rest of your code as opaque pointers to enforce that all other code has to go through these accompanying functions to use this struct. Or you might just make it be convention. An OOP language is just one that has language features to facilitate this, such as by allowing you to define methods within a type rather than functions outside of it.
Once you have this, then you might start thinking of features that might be nice to have alongside, such as dynamic typing, dynamic dispatch, inheritance, etc. But I don't think any of those are fundamentally OOP, they just fit well and are often found together.
Since having functions that belong to specific types isn't a very big concept, discussion about OOP is going to focus on these other things, which are not necessarily omnipresent and are often different across languages. So the discussion is either going to be very specific to one particular language (maybe even one particular set of libraries for it) or very vague.
> I see OOP as fundamentally one key concept: that there are functions which "belong to" specific types, and all other operations on those types go through the functions that "belong to" them
I would go even further:
All programming essentially consists of two things only:
1. data structures (often, but not necessarily, formalised as "types")
2. mechanisms to transform that data (functions, methods, algorithms, tasks, whatever)
Programming paradigms essentially describe different ways to organise these two: functional programming, for example, keeps the two strictly separate; in OOP the mechanisms are "attached" to the data they operate on. Of course, in practice there are many combinations and variations, with more and less flexibility (from strictly closed objects to mixins and similar), but this is the foundation.
So you say we don't need OOP because you can use this half-baked (non type-safe) way of implementing your favorite OOP feature yourself in your favorite touring-complete language? While omitting the key concept of virtual functions.
"OOP" is a philosophy more than specific implementation details. I've programmed "object orientedly" in any number of languages, many of those with no object orientation helping syntax, like C, assembly, Forth (although that one's more malleable). It's always been about the mental model of your programming, anything else is syntax sugar.
All discussion about "your not doing true OOP because $REASON" arguments are useless, repetitive, and discouraging to exploration, discovery and learning.
I'm not sure where you got "we don't need" from, unless you consider any language feature to be unnecessary if there's a way to implement the concept without it. Which is not a very useful way of looking at language features! You can implement anything in assembly, that doesn't mean higher-level languages aren't needed.
I'm saying that OOP as a concept is simple enough that you can do the basics of it in pretty idiomatic and straightforward C, and all the other stuff that we associate with "OOP" is not really universal. And thus it's no surprise that discussions around OOP as a general concept tend to be vague and not very useful.
Why do you seem to feel that OOP defines a specific set of features, for instance virtual functions?
There are many different OOP styles out there. All one needs to do is to look at the languages that originated the concept, such as SIMULA and Smalltalk to see that they weren't channeling the exact same ideas.
You can do OOP in a language like C by writing your code as structs and accompanying functions that operate on them. You might expose the structs to the rest of your code as opaque pointers to enforce that all other code has to go through these accompanying functions to use this struct. Or you might just make it be convention. An OOP language is just one that has language features to facilitate this, such as by allowing you to define methods within a type rather than functions outside of it.
Once you have this, then you might start thinking of features that might be nice to have alongside, such as dynamic typing, dynamic dispatch, inheritance, etc. But I don't think any of those are fundamentally OOP, they just fit well and are often found together.
Since having functions that belong to specific types isn't a very big concept, discussion about OOP is going to focus on these other things, which are not necessarily omnipresent and are often different across languages. So the discussion is either going to be very specific to one particular language (maybe even one particular set of libraries for it) or very vague.