In Apex Code, the SOQL engine automatically includes RecordTypeId and Id in any query result, even if you don't ask for it. There are a few other hidden parameters that appear in a query when presented to the SOQL engine.
For example, if you don't include a LIMIT statement, or set it to higher than
1+Limits.getLimitQueryRows()-Limits.getQueryRows(), the system automatically adds or changes the limit to
LIMIT :1+Limits.getLimitQueryRows()-Limits.getQueryRows(). This is how the governor limits are enforced. The exception to this is for Batchable classes that use a Database.getQueryLocator statement, where the limit is set 50,000,001 rows. Also, when you don't specify ALL ROWS, Apex Code silently includes a filter
IsDeleted = FALSE. In other words, whatever you put into your code isn't necessarily what will be executed by the SOQL engine.
Note that this means the following two queries are (approximately) the same in Apex Code:
Account accounts1 = [SELECT Id, RecordTypeId, Name, (SELECT Id, RecordType, Name FROM Contacts WHERE IsDeleted = FALSE) FROM Account WHERE IsDeleted = FALSE LIMIT :1+Limits.getLimitQueryRows()-Limits.getQueryRows()];
Account accounts2 = [SELECT Name, (SELECT Name FROM Contacts) FROM Account];
I don't know if there's a documented reason why RecordTypeId was specifically included, while audit fields were not, but I seem to recall that this behavior did not always exist in Apex Code, which suggests to me that some new SObject method was added that required this field to be present to function correctly. My best guess is that this field was needed for the Visualforce runtime engine to correctly render dynamic picklists on records.