So, don't quote me on this (also correct me if I'm wrong), but what I believe may be happening, is you create your task as a child task from the Main actor, so the work is being performed on the main actor, and then you lock the main actor with a semaphore, causing a deadlock. You could try using Task.detached, but it's not a good solution, because, as it was rightfully pointed out to you in the comments, don't mix semaphores with tasks. Not only it defeats the whole purpose, but it also may cause unintended consequences. Well, it already has, hasn't it? :)
The better approach would be to go all in on Swift Concurrency, and just do the work you need to do right within the Task. If you need to do it on another thread / actor, you can do it by creating a nested Task within your Task. But if it's already running on the Main Actor (again, better check, I'm not sure), can't you simply use code like this?
func didClickStart() {
scene.presentLoadingScreen()
var models: [String:Entity]!
Task {
models = await loadEntitiesInParallel(fns: entities, tr: tr)
scene.presentGame(models: models)
// Alternatively, if needed:
//MainActor.run { scene.presentGame(models: models) }
}
}