Well since nobody responded to my questions (presumably because nobody who visits here often enough knows anything about this approach, which was a little bit sad), I was able to solve my own problem. So, for future crazy people who want to implement a similar type of thing on their own, I will now outline my approach, technique, and results.
Step 1: Don't use btScaledBvhTriangleMeshShape, use btTriangleShape instead
Or even btBvhTriangleMeshShape for that matter. I haven't tried running any dynamic scenes or anything with Bullet, as my only objective for this little project was to utilize the collision detection engine (and not the physics, etc...), so I don't actually know if this step applies to those types of scenes. But what I can say for my application is this: btBvhTriangleMeshShape is
useless. Why is it useless? Well, simply because I couldn't find any way of obtaining the normal of the triangle of the surface a given entity is colliding with. I thoroughly scanned the documentation, and found no such thing. If this doesn't hold true for some reason, and there really is a way of obtaining the triangle normal (NOT the collision manifold normal, those are two very different things), then consider me an idiot and disregard this step. Although, it would have been nice for someone to tell me I could actually do this earlier in this thread, but I digress. Just use btTriangleShape, as it just so happens to give you the ability to calculate the shape's normal, which can be accessed via the collision manifold. Also, as I stated before, I couldn't get btAdjustInternalEdgeContacts/btGenerateInternalEdgeInfo working at all. I still have no idea why, and I don't even care anymore.
Step 2: Pre-processing (detecting shared edges of triangles)
As we're reading in vertices from the mesh we exported from Blender (via Ogre3D in this case), we need to identify which triangles are sharing edges with other triangles, and what those edges are. We also want to store this information in the btTriangleShape object's userPointer so that we can look it up when it comes time to resolve the collision. Moreover, we only want to mark edges as being shared that have the same (or very similar) normals. This makes it so that entities can't tunnel through walls at convex corners. I accomplished this by keeping a map of vertex index pairs to a list of indexes of btTriangleShape wrappers. The wrappers just keep the shape together with the edge info so that I can refer to this later via the shape's userPointer. So we just keep adding triangles, and check all the edges on the map to see if there already is an edge with the same two vertex indexes, and if there is, we check the normals of the two triangles. If the normals are within a certain threshold, we mark the edges in both of the shape wrappers as being disabled. Because of the fact that the points in the btTriangleShape are always stored in the same order, we can easily interpret the bools for the edge flags later simply by storing them as a bool[3] array, which will come in handy during processing.
Step 3: Collision Resolution (Ignoring the flagged edges)
So, once the triangles have all been added to the scene, we can just do standard collision detection. For the resolution of each manifold between an entity and the static geometry triangles, I do the following. First: I just find the smallest negative distance manifold point. Once I found this manifold point, I simply move the entity in the distance of the manifold's normal. So where does the edge detection come into play? We do this in an isValid(btManifoldPoint& mp) function. This function originally just returned true if the distance of the manifold point was < 0. Now, I needed to make sure that manifold points along disabled edges were thrown out. To do this, I get the triangle shape's normal, and check the angle between that and the manifold normal. If this angle is greater than some threshold, then we know that the manifold is on the edge of the triangle. So all we need to do is check the squared distances of the world-space manifold point on the triangle with all 3 vertices, and find the largest one. The point with the largest distance is the point of the triangle we don't care about, and our edge in question is made of the other two points. And because the edge flags are in order relative to the order of the triangle's points, we know which index in our bool[3] array to check! If this flag at that edge index is false, we return false. Sooooo easy!
Results:
There were a few other minor tweaks I had to do to get some weird edge cases working. For example: I had this situation where if you were moving capsule into a 90-degree concave corner, you could phase through the corner. Interestingly enough, it was only doing this when both directions were being held down simultaneously. I found out this was because when one of the walls resolved the collision, on the next manifold it would call refreshContactPoints and the manifold on the other wall would no longer have valid manifold points! I solved this by simply calling manifold->setContactBreakingThreshold(1.f) on manifolds that had static geometry in them before the call to refreshContactPoints happens. I have no idea if that number is a reasonable value or not, if even if that's the best place to call it, but it seems to work fine. Another obvious result is that this solution doesn't account for tunneling. If shapes are moving fast enough, you can in fact clip through the triangle mesh static geometry. I experimented a bit and found that setting a maximum speed of 32 units-per-second was a good number for preventing tunneling through static geometry (although it can still happen rarely sometimes). For my own purposes, this is an acceptable result; I don't really care about fast-moving physical objects anyway, and there are plenty of entertaining applications that can be made with this level of error (OoT, anyone?).
Here are some videos of my implementation working on a few edge cases:
https://www.youtube.com/watch?v=lz9KpFDIxsM
https://www.youtube.com/watch?v=dJ3M1Q5mz0E
I can't wait for someone to come around to let me know how (in)efficient my solution is...