Identify root collection?
The master collection of the current scene can be accessed using bpy.context.scene.collection. You can access all scenes regardless of the one currently active through bpy.data.scenes. For example if you want to access a scene by name and get its master collection, you could use bpy.data.scenes["Scene"].collection. Alternatively you can also access them by index.

Once you have the master collection of the scene you can get all objects regardless of the sub-collections within, using all_objects. So for example bpy.context.scene.collection.all_objects would give you all objects in the current scene. You can iterate through them like this (which prints the name of the objects):

for obj in bpy.context.scene.collection.all_objects:
    print(obj.name)


Regarding your other questions:


Yes Python requires indentation to determine what part of the code is within a specific scope. I recommend using an editor that helps you write syntactically correct code.
Add-on can add their own variables using code similar to the one you described and it's precisely so that they can store custom data. They can conflict each other if the have the same name, which is why their name is usually based on the add-on and often bundle their configuration in a PointerProperty which points to an object that contains the actual variables/settings/properties that the add-on uses. This avoids adding several variables.
Correct, the scene variable just holds a references to the scene object.
Correct. I think this works (might be slightly wrong) because SceneObjects, which is the type of bpy.context.scene.objects, implements a custom __eq__ method, that allows to determine equality by their unique name. If their names are equal they are the same object.


You can actually test of lot of these interactively in Blender's scripting workspace where you have a console that let's you run these commands in Blender without having to write an add-on right away.