The answer appears to be...
Here is a quick manual proof-of-concept using Drivers, as in the tutorial ref'd below and in Comments on the original Question, which I heartily recommend. My text explanation here will not be nearly so clear and user-friendly as the video, so I suggest anyone who is interested should watch it!
These cubes are copies of one cube (manually created, not scripted) which has random-number drivers (manually created and tweaked via the UI) attached to various properties -- Scale (xyz), Diffuse Colour, Diffuse Intensity, and the geometry of the Marble generated texture. The result is fairly pleasing, though I would like to know whether/how I can overcome the signature diagonal grain of the generated texture. In a game landscape these cubes would probably pass OK as "different" from one another, despite the grain issue.
1) expose some kind of python random function(s) to driver context, as shown in this vimeo tutorial:
(sample rand function code is provided by the author via a link on this vimeo page, and this is what I used:).
def randf(lo, hi):
return random.uniform(lo, hi)
def randi(lo, hi):
return random.randint(lo, hi)
def gauss(mean, stdev):
return random.gauss(mean, stdev)
bpy.app.driver_namespace["randf"] = randf
bpy.app.driver_namespace["randi"] = randi
bpy.app.driver_namespace["gauss"] = gauss
2) With object selected: in a property editing pane, select an object property such as Scale, RGB value in Material, etc. Other-click on it (whichever one you don't use for regular selection of objects, focus placement, etc). When menu comes up, choose "Add Driver" and then select either the whole group or just one property, to be "set manually later". The slider should turn purple to indicate that it's being driven.
3) Open a Graph Editor and reveal its property editing panel (N). Choose the Driver tab. Now you can select your driver (at left) and edit/tweak it in the pane. I for example would choose Scripted Expression for my randomising drivers, and for the expression enter my randf function which takes 2 args, min and max. For some properties (material and texture props, so far) you have to set a driver variable. For others (pos/rot/scale I believe) you can skip it... I don't know why there is a difference. If you've done it all correctly, then when you press Update Dependencies, your object should randomly change its shape, position, rotation, colour, whatever the driver is randomising -- in my case, within the bounds set by the 2 args to the rand function. Anything that causes a Scene Update should trigger this regeneration of the random values, but you can "turn off" the drivers using the "mute buttons" next to their entries in the GE left hand pane, freezing the object's appearance.
4) When you Duplicate this "driven" object, the drivers go with it and the cloned object also has the randomised property. However, aspects of the original object are by default shared with its dupes. So for example, material seems to be shared with dupes; you can spawn a new cube by duplication but it will have the same (random) colour as the original; they will both change to the identical new value when Update Dependencies is invoked. To fix this, I selected all the objects (dupes and original), hit U, and picked Materials/Texture. This apparently prevents sharing. Each cube now has its own independent material/texture settings and finds its own random colour/texture when the update happens. You can dupe the dupes and the inheritance continues; the original object can even be deleted without losing any of your hard work. But whenever you dupe, you have to turn off material sharing if you want randomised material properties.
Conclusion: Drivers are insanely powerful; also -- potentially -- insanely fun. It actually boggles the mind, thinking of all the strange and wonderful ways that properties could be driven by, say, other objects' properties, frame count, external data files, higher math functions... But my immediate practical need was to make random small variations in mass produced objects, and Drivers seem to be the solution of choice. My next challenge is to create drivers under Python control. If I succeed, there will be one more photo in this series :-)
Another interesting write-up on Drivers may be found here
some of it is a bit beyond me right now, but I hope to understand it better in a day or so.